Skip to content

Commit 7c27e32

Browse files
author
Bill Prin
committed
Add Async Background Thread transport
Refactors handlers into separate package Adds background threaded transport Adds fix to Batch commit to properly set log name
1 parent 2978c85 commit 7c27e32

20 files changed

+710
-34
lines changed

docs/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@
110110
logging-metric
111111
logging-sink
112112
logging-handlers
113+
logging-transports-sync
114+
logging-transports-thread
115+
logging-transports-base
113116

114117
.. toctree::
115118
:maxdepth: 0

docs/logging-handlers.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Python Logging Module Handler
22
==============================
33

4-
.. automodule:: gcloud.logging.handlers
4+
.. automodule:: gcloud.logging.handlers.handlers
55
:members:
66
:show-inheritance:
77

docs/logging-transports-base.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Python Logging Handler Sync Transport
2+
======================================
3+
4+
.. automodule:: gcloud.logging.handlers.transports.base
5+
:members:
6+
:show-inheritance:
7+

docs/logging-transports-sync.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Python Logging Handler Sync Transport
2+
======================================
3+
4+
.. automodule:: gcloud.logging.handlers.transports.sync
5+
:members:
6+
:show-inheritance:
7+

docs/logging-transports-thread.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Python Logging Handler Threaded Transport
2+
=========================================
3+
4+
5+
.. automodule:: gcloud.logging.handlers.transports.background_thread
6+
:members:
7+
:show-inheritance:
8+
9+

docs/logging-usage.rst

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,21 @@ Logging client.
333333
>>> cloud_logger = logging.getLogger('cloudLogger')
334334
>>> cloud_logger.setLevel(logging.INFO) # defaults to WARN
335335
>>> cloud_logger.addHandler(handler)
336-
>>> cloud_logger.error('bad news') # API call
336+
>>> cloud_logger.error('bad news')
337337

338338
.. note::
339339

340-
This handler currently only supports a synchronous API call, which means each logging statement
341-
that uses this handler will require an API call.
340+
This handler by default uses an asynchronous transport that sends log entries on a background
341+
thread. However, the API call will still be made in the same process. For other transport
342+
options, see the transports section.
343+
344+
All logs will go to a single custom log, which defaults to "python". The name of the Python
345+
logger will be included in the structured log entry under the "python_logger" field. You can
346+
change it by providing a name to the handler:
347+
348+
.. doctest::
349+
350+
>>> handler = CloudLoggingHandler(client, name="mycustomlog")
342351

343352
It is also possible to attach the handler to the root Python logger, so that for example a plain
344353
`logging.warn` call would be sent to Cloud Logging, as well as any other loggers created. However,
@@ -355,4 +364,25 @@ this automatically:
355364
>>> handler = CloudLoggingHandler(client)
356365
>>> logging.getLogger().setLevel(logging.INFO) # defaults to WARN
357366
>>> setup_logging(handler)
358-
>>> logging.error('bad news') # API call
367+
>>> logging.error('bad news')
368+
369+
You can also exclude certain loggers:
370+
371+
.. doctest::
372+
373+
>>> setup_logging(handler, excluded_loggers=('werkzeug',)))
374+
375+
376+
377+
Python logging handler transports
378+
==================================
379+
380+
The Python logging handler can use different transports. The default is
381+
:class:`gcloud.logging.handlers.BackgroundThreadTransport`.
382+
383+
1. :class:`gcloud.logging.handlers.BackgroundThreadTransport` this is the default. It writes
384+
entries on a background :class:`python.threading.Thread`.
385+
386+
1. :class:`gcloud.logging.handlers.SyncTransport` this handler does a direct API call on each
387+
logging statement to write the entry.
388+

gcloud/logging/handlers/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
# Copyright 2016 Google Inc. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Python :mod:`logging` handlers for Google Cloud Logging."""
17+
18+
from gcloud.logging.handlers.handlers import CloudLoggingHandler, setup_logging

gcloud/logging/handlers.py renamed to gcloud/logging/handlers/handlers.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616

1717
import logging
1818

19+
from gcloud.logging.handlers.transports import BackgroundThreadTransport
20+
1921
EXCLUDE_LOGGER_DEFAULTS = (
2022
'gcloud',
21-
'oauth2client.client'
23+
'oauth2client'
2224
)
2325

26+
DEFAULT_LOGGER_NAME = "python"
27+
2428

2529
class CloudLoggingHandler(logging.StreamHandler, object):
2630
"""Python standard logging handler to log messages to the Google Cloud
@@ -36,6 +40,17 @@ class CloudLoggingHandler(logging.StreamHandler, object):
3640
:type client: :class:`gcloud.logging.client`
3741
:param client: the authenticated gcloud logging client for this handler
3842
to use
43+
:type name: str
44+
:param name: the name of the custom log in Stackdriver Logging. Defaults
45+
to "python". The name of the Python logger will be represented
46+
in the "python_logger" field.
47+
48+
:type transport: :class:`gcloud.logging.handlers.transports.Transport`
49+
:param transport: the class object to instantiate. It should extend from
50+
the base Transport type and implement
51+
:meth`gcloud.logging.handlers.transports.base.Transport.send`
52+
Defaults to BackgroundThreadTransport. The other
53+
option is SyncTransport.
3954
4055
Example:
4156
@@ -55,9 +70,13 @@ class CloudLoggingHandler(logging.StreamHandler, object):
5570
5671
"""
5772

58-
def __init__(self, client):
73+
def __init__(self, client,
74+
name=DEFAULT_LOGGER_NAME,
75+
transport=BackgroundThreadTransport):
5976
super(CloudLoggingHandler, self).__init__()
77+
self.name = name
6078
self.client = client
79+
self.transport = transport(client, name)
6180

6281
def emit(self, record):
6382
"""
@@ -66,13 +85,11 @@ def emit(self, record):
6685
See: https://docs.python.org/2/library/logging.html#handler-objects
6786
"""
6887
message = super(CloudLoggingHandler, self).format(record)
69-
logger = self.client.logger(record.name)
70-
logger.log_struct({"message": message},
71-
severity=record.levelname)
88+
self.transport.send(record, message)
7289

7390

7491
def setup_logging(handler, excluded_loggers=EXCLUDE_LOGGER_DEFAULTS):
75-
"""Helper function to attach the CloudLoggingAPI handler to the Python
92+
"""Helper function to attach the CloudLogging handler to the Python
7693
root logger, while excluding loggers this library itself uses to avoid
7794
infinite recursion
7895
@@ -90,11 +107,11 @@ def setup_logging(handler, excluded_loggers=EXCLUDE_LOGGER_DEFAULTS):
90107
91108
import logging
92109
import gcloud.logging
93-
from gcloud.logging.handlers import CloudLoggingAPIHandler
110+
from gcloud.logging.handlers import CloudLoggingHandler
94111
95112
client = gcloud.logging.Client()
96113
handler = CloudLoggingHandler(client)
97-
setup_logging(handler)
114+
gcloud.logging.setup_logging(handler)
98115
logging.getLogger().setLevel(logging.DEBUG)
99116
100117
logging.error("bad news") # API call

gcloud/logging/test_handlers.py renamed to gcloud/logging/handlers/test_handlers.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,32 @@ class TestCloudLoggingHandler(unittest2.TestCase):
2323
PROJECT = 'PROJECT'
2424

2525
def _getTargetClass(self):
26-
from gcloud.logging.handlers import CloudLoggingHandler
26+
from gcloud.logging.handlers.handlers import CloudLoggingHandler
2727
return CloudLoggingHandler
2828

2929
def _makeOne(self, *args, **kw):
3030
return self._getTargetClass()(*args, **kw)
3131

3232
def test_ctor(self):
3333
client = _Client(self.PROJECT)
34-
handler = self._makeOne(client)
34+
handler = self._makeOne(client, transport=_Transport)
3535
self.assertEqual(handler.client, client)
3636

3737
def test_emit(self):
3838
client = _Client(self.PROJECT)
39-
handler = self._makeOne(client)
39+
handler = self._makeOne(client, transport=_Transport)
4040
LOGNAME = 'loggername'
4141
MESSAGE = 'hello world'
4242
record = _Record(LOGNAME, logging.INFO, MESSAGE)
4343
handler.emit(record)
44-
self.assertEqual(client.logger(LOGNAME).log_struct_called_with,
45-
({'message': MESSAGE}, logging.INFO))
44+
45+
self.assertEqual(handler.transport.send_called_with, (record, MESSAGE))
4646

4747

4848
class TestSetupLogging(unittest2.TestCase):
4949

5050
def _callFUT(self, handler, excludes=None):
51-
from gcloud.logging.handlers import setup_logging
51+
from gcloud.logging.handlers.handlers import setup_logging
5252
if excludes:
5353
return setup_logging(handler, excluded_loggers=excludes)
5454
else:
@@ -95,20 +95,10 @@ def release(self):
9595
pass # pragma: NO COVER
9696

9797

98-
class _Logger(object):
99-
100-
def log_struct(self, message, severity=None):
101-
self.log_struct_called_with = (message, severity)
102-
103-
10498
class _Client(object):
10599

106100
def __init__(self, project):
107101
self.project = project
108-
self.logger_ = _Logger()
109-
110-
def logger(self, _): # pylint: disable=unused-argument
111-
return self.logger_
112102

113103

114104
class _Record(object):
@@ -123,3 +113,12 @@ def __init__(self, name, level, message):
123113

124114
def getMessage(self):
125115
return self.message
116+
117+
118+
class _Transport(object):
119+
120+
def __init__(self, client, name):
121+
pass
122+
123+
def send(self, record, message):
124+
self.send_called_with = (record, message)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python
2+
# Copyright 2016 Google Inc. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Transport classes for Python logging integration
17+
18+
Currently two options are provided, a synchronous transport that makes
19+
an API call for each log statement, and an asynchronous handler that
20+
sends the API using a :class:`gcloud.logging.Batch` object in the background.
21+
"""
22+
23+
from gcloud.logging.handlers.transports.base import Transport
24+
from gcloud.logging.handlers.transports.sync import SyncTransport
25+
from gcloud.logging.handlers.transports.background_thread import (
26+
BackgroundThreadTransport)

0 commit comments

Comments
 (0)