Skip to content

Commit a62781c

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 133ca48 commit a62781c

20 files changed

+780
-51
lines changed

docs/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@
111111
logging-metric
112112
logging-sink
113113
logging-handlers
114+
logging-transports-sync
115+
logging-transports-thread
116+
logging-transports-base
114117

115118
.. toctree::
116119
: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
@@ -396,12 +396,21 @@ Logging client.
396396
>>> cloud_logger = logging.getLogger('cloudLogger')
397397
>>> cloud_logger.setLevel(logging.INFO) # defaults to WARN
398398
>>> cloud_logger.addHandler(handler)
399-
>>> cloud_logger.error('bad news') # API call
399+
>>> cloud_logger.error('bad news')
400400

401401
.. note::
402402

403-
This handler currently only supports a synchronous API call, which means each logging statement
404-
that uses this handler will require an API call.
403+
This handler by default uses an asynchronous transport that sends log entries on a background
404+
thread. However, the API call will still be made in the same process. For other transport
405+
options, see the transports section.
406+
407+
All logs will go to a single custom log, which defaults to "python". The name of the Python
408+
logger will be included in the structured log entry under the "python_logger" field. You can
409+
change it by providing a name to the handler:
410+
411+
.. doctest::
412+
413+
>>> handler = CloudLoggingHandler(client, name="mycustomlog")
405414

406415
It is also possible to attach the handler to the root Python logger, so that for example a plain
407416
`logging.warn` call would be sent to Cloud Logging, as well as any other loggers created. However,
@@ -418,4 +427,25 @@ this automatically:
418427
>>> handler = CloudLoggingHandler(client)
419428
>>> logging.getLogger().setLevel(logging.INFO) # defaults to WARN
420429
>>> setup_logging(handler)
421-
>>> logging.error('bad news') # API call
430+
>>> logging.error('bad news')
431+
432+
You can also exclude certain loggers:
433+
434+
.. doctest::
435+
436+
>>> setup_logging(handler, excluded_loggers=('werkzeug',)))
437+
438+
439+
440+
Python logging handler transports
441+
==================================
442+
443+
The Python logging handler can use different transports. The default is
444+
:class:`gcloud.logging.handlers.BackgroundThreadTransport`.
445+
446+
1. :class:`gcloud.logging.handlers.BackgroundThreadTransport` this is the default. It writes
447+
entries on a background :class:`python.threading.Thread`.
448+
449+
1. :class:`gcloud.logging.handlers.SyncTransport` this handler does a direct API call on each
450+
logging statement to write the entry.
451+

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)