Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GAE and GKE fluentd Handlers #2668

Merged
merged 1 commit into from
Nov 22, 2016
Merged

Conversation

waprin
Copy link
Contributor

@waprin waprin commented Nov 3, 2016

On GAE and GKE with the plugin installed, there is a fluentd plugin that collects logs from files. However without the right formatting, metadata like log_level is lost. Furthermore, the fluentd agents are configured to set the correct resources types, which could be done in the main handler as well, but it’s easier to rely on the fluentd configurations.

This adds two new handlers and some helper functions to detect when they should be used.

@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Nov 3, 2016
@waprin
Copy link
Contributor Author

waprin commented Nov 3, 2016

@dhermes /cc @jonparrott

Copy link
Contributor

@daspecster daspecster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty awesome stuff!

@@ -0,0 +1,6 @@
Google App Engine Flexible Log Handler
=======================================

This comment was marked as spam.

This comment was marked as spam.

@@ -0,0 +1,6 @@
Google Container Engine Log Handler
=======================================

This comment was marked as spam.

This comment was marked as spam.

Logging client.
It's possible to tie the Python :mod:`logging` module directly into Google Stackdriver Logging.
There are different handler options to accomplish this. To automatically pick the default,
use :meth:``google.cloud.logging.client.get_default_handler`.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

>>> cloud_logger = logging.getLogger('cloudLogger')
>>> cloud_logger.setLevel(logging.INFO) # defaults to WARN
>>> cloud_logger.addHandler(handler)
>>> cloud_logger.error('bad news')

It is also possible to attach the handler to the root Python logger, so that for example a plain
`logging.warn` call would be sent to Cloud Logging, as well as any other loggers created. However,

This comment was marked as spam.

This comment was marked as spam.

method :meth:`setup_logging <google.cloud.logging.handlers.setup_logging>` is provided to configure
this automatically:
CloudLoggingHandler
=======================

This comment was marked as spam.

This comment was marked as spam.

@@ -15,4 +15,6 @@
"""Python :mod:`logging` handlers for Google Cloud Logging."""

from google.cloud.logging.handlers.handlers import CloudLoggingHandler
from google.cloud.logging.handlers.GAEHandler import GAEHandler

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

:param excluded_loggers: The loggers to not attach the handler to. This
will always include the loggers in the path of
the logging client itself.
:param excluded_loggers: (Optional)The loggers to not attach the handler

This comment was marked as spam.

This comment was marked as spam.

to. This will always include the loggers in the
path of the logging client itself.

:type log_level: :mod:`logging` log level

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@@ -184,7 +184,7 @@ def batch(self):

class _Client(object):

def __init__(self, project):
def __init__(self, project, _=None, __=None):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@@ -584,6 +637,9 @@ def create_scoped(self, scope):
self._scopes = scope
return self

def authorize(self, http):
pass

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

Logs to the well-known file that the fluentd sidecar container on App Engine
Flexible is configured to read from and send to Stackdriver Logging.

This file is largely copied from:

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

import math
import os

LOG_PATH_TEMPLATE = '/var/log/app_engine/app.{pid}.json'

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

LOG_FILE_COUNT = 3


class GAEHandler(logging.handlers.RotatingFileHandler):

This comment was marked as spam.

This comment was marked as spam.



class GAEHandler(logging.handlers.RotatingFileHandler):
"""A handler that writes to the GAE fluentd Stackdriver logging file.

This comment was marked as spam.

This comment was marked as spam.

class GAEHandler(logging.handlers.RotatingFileHandler):
"""A handler that writes to the GAE fluentd Stackdriver logging file.

Writes to the file that the fluentd agent on App Engine Flexbile is

This comment was marked as spam.

target=self._run,
name='google.cloud.logging.handlers.transport.Worker')
thread_name = 'google.cloud.logging.handlers.transport.Worker'
self._thread = threading.Thread(target=self._run,

This comment was marked as spam.

This comment was marked as spam.

LOGNAME = 'loggername'
MESSAGE = 'hello world'
record = _Record(LOGNAME, logging.INFO, MESSAGE)
expected_payload = {'message': MESSAGE,

This comment was marked as spam.

self.assertEqual(payload, json.dumps(expected_payload))


class _Record(object):

This comment was marked as spam.

This comment was marked as spam.

import logging
import json
handler = self._makeOne()
LOGNAME = 'loggername'

This comment was marked as spam.

This comment was marked as spam.

@@ -184,7 +184,7 @@ def batch(self):

class _Client(object):

def __init__(self, project):
def __init__(self, project, _=None, __=None):

This comment was marked as spam.

@theacodes theacodes self-assigned this Nov 3, 2016
@@ -110,6 +110,8 @@
logging-metric
logging-sink
logging-handlers
logging-handlers-GAEHandler
logging-handlers-GKEHandler

This comment was marked as spam.

This comment was marked as spam.

Google App Engine Flexible Log Handler
=======================================

.. automodule:: google.cloud.logging.handlers.GAEHandler

This comment was marked as spam.

This comment was marked as spam.

Google Container Engine Log Handler
=======================================

.. automodule:: google.cloud.logging.handlers.GKEHandler

This comment was marked as spam.

This comment was marked as spam.

create a :class:`CloudLoggingHandler <google.cloud.logging.CloudLoggingHandler>` instance from your
Logging client.
It's possible to tie the Python :mod:`logging` module directly into Google Stackdriver Logging.
There are different handler options to accomplish this. To automatically pick the default,

This comment was marked as spam.

This comment was marked as spam.

Logging client.
It's possible to tie the Python :mod:`logging` module directly into Google Stackdriver Logging.
There are different handler options to accomplish this. To automatically pick the default,
use :meth:``google.cloud.logging.client.get_default_handler`.

This comment was marked as spam.


client = self._makeOne(self.PROJECT, credentials=_Credentials())

with _Monkey(os, getenv=lambda var: var == 'GAE_APPENGINE_HOSTNAME'):

This comment was marked as spam.


self.assertIsInstance(handler, GKEHandler)

with _Monkey(os, getenv=lambda _: None):

This comment was marked as spam.


self.assertIsInstance(handler, GAEHandler)

with _Monkey(os, getenv=lambda var: var == 'KUBERNETES_SERVICE'):

This comment was marked as spam.

This comment was marked as spam.

def test_setup_logging(self):
from google.cloud._testing import _Monkey
import google.cloud.logging.client as MUT
client = self._makeOne(

This comment was marked as spam.

This comment was marked as spam.

self.called = False

def __call__(self, _, log_level,
excluded_loggers=None): # pylintdisable=unused-variable

This comment was marked as spam.

This comment was marked as spam.

@waprin
Copy link
Contributor Author

waprin commented Nov 4, 2016

Think I addressed all comments, let me make sure Travis is passing and then I'll do another once-over before I request another review.

@@ -390,11 +391,12 @@ Delete a sink:
False

Integration with Python logging module
---------------------------------------------
---------------------------------------

This comment was marked as spam.

@@ -596,7 +596,7 @@ def test_get_default_handler_container_engine(self):
client = self._makeOne(project=self.PROJECT,
credentials=_Credentials(),
use_gax=False)
os.environ[_CONTAINER_ENGINE_ENV] = True
os.environ[_CONTAINER_ENGINE_ENV] = "True"

This comment was marked as spam.

(i.e. your app.yaml contains ``runtime: python``), and
:class:`~google.cloud.handlers.container_engine.ContainerEngineHandler`,
which is
recommended when running on `Google Container Engine` with the Stackdriver

This comment was marked as spam.

from google.cloud.logging.handlers import AppEngineHandler
from google.cloud.logging.handlers import ContainerEngineHandler
from google.cloud.logging.handlers import setup_logging
from google.cloud.logging.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS

This comment was marked as spam.

This comment was marked as spam.

"""

# This file is largely copied from:
# https://github.com/GoogleCloudPlatform/python-compat-runtime/blob/master/appengine-vmruntime/vmruntime/cloud_logging.py

This comment was marked as spam.

>>> setup_logging(handler, excluded_loggers=('werkzeug',)))
>>> handler = CloudLoggingHandler(client, name="mycustomlog")

fluentd logging handlers

This comment was marked as spam.

the environment to automatically detect whether the code is running in
these platforms and use the appropriate handler.

In both cases, the fluentd agent is configured to automatically parse log files

This comment was marked as spam.

This comment was marked as spam.

method :meth:`setup_logging <google.cloud.logging.handlers.setup_logging>` is provided to configure
this automatically:
If you prefer not to use :meth:`google.cloud.logging.client
.get_default_handler`, you can directly create a

This comment was marked as spam.

This comment was marked as spam.

'message': message,
'timestamp': {
'seconds': 5,
'nanos': int(.03 * 1e9)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

'timestamp': {'seconds': 5,
'nanos': int(.03 * 1e9)},
'thread': record.thread,
'severity': record.levelname}

This comment was marked as spam.

This comment was marked as spam.

'nanos': int(subsecond * 1e9)
},
'thread': record.thread,
'severity': record.levelname

This comment was marked as spam.

This comment was marked as spam.

'message': message,
'timestamp': {
'seconds': int(second),
'nanos': int(subsecond * 1e9)

This comment was marked as spam.

This comment was marked as spam.

payload = {
'message': message,
'timestamp': {'seconds': int(second),
'nanos': int(subsecond * 1e9)},

This comment was marked as spam.

This comment was marked as spam.

@waprin waprin force-pushed the newhandlers branch 4 times, most recently from c755a71 to c132584 Compare November 16, 2016 21:50
class AppEngineHandler(logging.handlers.RotatingFileHandler):
"""A handler that writes to the App Engine fluentd Stackdriver log file.

Writes to the file that the fluentd agent on App Engine Flexbile is

This comment was marked as spam.

This comment was marked as spam.

itself, and not as a formatting step, so as not to interfere with
user-defined logging formats.

Logging calls can also alternatively 'trace_id' in as a field in the

This comment was marked as spam.

This comment was marked as spam.

"""Logging handler for Google Container Engine (GKE).

Formats log messages in a JSON format, so that Kubernetes clusters with the
fluentd google cloud plugin installed can format their log messages so that

This comment was marked as spam.

This comment was marked as spam.



class ContainerEngineHandler(logging.StreamHandler):
"""Handler to formats log messages in good fluentd GKE format.

This comment was marked as spam.

This comment was marked as spam.

@@ -15,27 +15,21 @@
"""Python :mod:`logging` handlers for Google Cloud Logging."""

import logging

This comment was marked as spam.

This comment was marked as spam.

class TestContainerEngineHandler(unittest.TestCase):
PROJECT = 'PROJECT'

def _getTargetClass(self):

This comment was marked as spam.

'message': message,
'timestamp': {
'seconds': 5,
'nanos': int(.03 * 1e9)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

'message': MESSAGE,
'python_logger': PYTHON_LOGGER_NAME
'message': message,
'python_logger': python_logger_name

This comment was marked as spam.

'message': MESSAGE,
'python_logger': PYTHON_LOGGER_NAME
'message': message,
'python_logger': python_logger_name

This comment was marked as spam.

This comment was marked as spam.

@@ -584,6 +637,9 @@ def create_scoped(self, scope):
self._scopes = scope
return self

def authorize(self, http):
pass

This comment was marked as spam.

@@ -562,6 +629,9 @@ def create_scoped(self, scope):
self._scopes = scope
return self

def authorize(self, _):

This comment was marked as spam.

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import mock

This comment was marked as spam.

use_gax=False)
handler = client.get_default_handler()
self.assertIsInstance(handler, CloudLoggingHandler)
self.assertTrue(credentials.authorized, http_mock)

This comment was marked as spam.

from google.cloud.logging.handlers import CloudLoggingHandler

http_mock = mock.Mock()

This comment was marked as spam.

from google.cloud._testing import _Monkey
import google.cloud.logging.client as MUT

http_mock = mock.Mock()

This comment was marked as spam.


def test_setup_logging(self):
@mock.patch('copy.deepcopy')
def test_setup_logging(self, deepcopy):

This comment was marked as spam.

@dhermes
Copy link
Contributor

dhermes commented Nov 21, 2016

@waprin Pretty please squash (either in the GitHub UI or in your local copy).

@jonparrott Do you want to do a final pass?

On GAE and GKE with the plugin installed,
there is a fluentd plugin that collects logs
from files. However without the right formatting,
metadata like log_level is lost. Furthermore,
the fluentd agents are configured to set the
correct resources types, which could be
done in the main handler as well, but it’s
easier to rely on the fluentd configurations.

This adds two new handlers and some helper
functions to detect when they should be used.
@dhermes dhermes merged commit 5d59291 into googleapis:master Nov 22, 2016
@daspecster daspecster added the api: logging Issues related to the Cloud Logging API. label Nov 22, 2016
richkadel pushed a commit to richkadel/google-cloud-python that referenced this pull request May 6, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: logging Issues related to the Cloud Logging API. cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants