Skip to content

Commit 8be2b0a

Browse files
authored
Logging: allow setting name, args on default handler (post-blacken) (#6828)
- Expose 'stream' argument to handler constructors. Defaults to 'None', passed to stdlib's 'logging.StreamHandler'. - Expose 'name' argument to 'ContainerEngineHandler'. * Add '**kw' to 'Client.{get_default_handler,setup_logging}'. Plumb them through to the underlying handler constructor. Closes #6206.
1 parent d64d5e2 commit 8be2b0a

File tree

8 files changed

+190
-38
lines changed

8 files changed

+190
-38
lines changed

google/cloud/logging/client.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,12 @@ def list_metrics(self, page_size=None, page_token=None):
317317
"""
318318
return self.metrics_api.list_metrics(self.project, page_size, page_token)
319319

320-
def get_default_handler(self):
320+
def get_default_handler(self, **kw):
321321
"""Return the default logging handler based on the local environment.
322322
323+
:type kw: dict
324+
:param kw: keyword args passed to handler constructor
325+
323326
:rtype: :class:`logging.Handler`
324327
:returns: The default log handler based on the environment
325328
"""
@@ -329,14 +332,14 @@ def get_default_handler(self):
329332
_APPENGINE_FLEXIBLE_ENV_VM in os.environ
330333
or _APPENGINE_INSTANCE_ID in os.environ
331334
):
332-
return AppEngineHandler(self)
335+
return AppEngineHandler(self, **kw)
333336
elif gke_cluster_name is not None:
334-
return ContainerEngineHandler()
337+
return ContainerEngineHandler(**kw)
335338
else:
336-
return CloudLoggingHandler(self)
339+
return CloudLoggingHandler(self, **kw)
337340

338341
def setup_logging(
339-
self, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS
342+
self, log_level=logging.INFO, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, **kw
340343
):
341344
"""Attach default Stackdriver logging handler to the root logger.
342345
@@ -354,6 +357,9 @@ def setup_logging(
354357
handler to. This will always include the
355358
loggers in the path of the logging client
356359
itself.
360+
361+
:type kw: dict
362+
:param kw: keyword args passed to handler constructor
357363
"""
358-
handler = self.get_default_handler()
364+
handler = self.get_default_handler(**kw)
359365
setup_logging(handler, log_level=log_level, excluded_loggers=excluded_loggers)

google/cloud/logging/handlers/app_engine.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,19 @@ class AppEngineHandler(logging.StreamHandler):
4646
:param transport: The transport class. It should be a subclass
4747
of :class:`.Transport`. If unspecified,
4848
:class:`.BackgroundThreadTransport` will be used.
49+
50+
:type stream: file-like object
51+
:param stream: (optional) stream to be used by the handler.
4952
"""
5053

5154
def __init__(
52-
self, client, name=_DEFAULT_GAE_LOGGER_NAME, transport=BackgroundThreadTransport
55+
self,
56+
client,
57+
name=_DEFAULT_GAE_LOGGER_NAME,
58+
transport=BackgroundThreadTransport,
59+
stream=None,
5360
):
54-
super(AppEngineHandler, self).__init__()
61+
super(AppEngineHandler, self).__init__(stream)
5562
self.name = name
5663
self.client = client
5764
self.transport = transport(client, name)

google/cloud/logging/handlers/container_engine.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,18 @@ class ContainerEngineHandler(logging.StreamHandler):
2929
3030
This handler is written to format messages for the Google Container Engine
3131
(GKE) fluentd plugin, so that metadata such as log level are properly set.
32+
33+
:type name: str
34+
:param name: (optional) the name of the custom log in Stackdriver Logging.
35+
36+
:type stream: file-like object
37+
:param stream: (optional) stream to be used by the handler.
3238
"""
3339

40+
def __init__(self, name=None, stream=None):
41+
super(ContainerEngineHandler, self).__init__()
42+
self.name = name
43+
3444
def format(self, record):
3545
"""Format the message into JSON expected by fluentd.
3646

google/cloud/logging/handlers/handlers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ class CloudLoggingHandler(logging.StreamHandler):
5858
:type labels: dict
5959
:param labels: (Optional) Mapping of labels for the entry.
6060
61+
:type stream: file-like object
62+
:param stream: (optional) stream to be used by the handler.
63+
6164
Example:
6265
6366
.. code-block:: python
@@ -74,7 +77,6 @@ class CloudLoggingHandler(logging.StreamHandler):
7477
cloud_logger.addHandler(handler)
7578
7679
cloud_logger.error('bad news') # API call
77-
7880
"""
7981

8082
def __init__(
@@ -84,8 +86,9 @@ def __init__(
8486
transport=BackgroundThreadTransport,
8587
resource=_GLOBAL_RESOURCE,
8688
labels=None,
89+
stream=None,
8790
):
88-
super(CloudLoggingHandler, self).__init__()
91+
super(CloudLoggingHandler, self).__init__(stream)
8992
self.name = name
9093
self.client = client
9194
self.transport = transport(client, name)

tests/unit/handlers/test_app_engine.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,9 @@ def _get_target_class(self):
2929
def _make_one(self, *args, **kw):
3030
return self._get_target_class()(*args, **kw)
3131

32-
def test_constructor(self):
33-
from google.cloud.logging.handlers.app_engine import _GAE_PROJECT_ENV_FLEX
34-
from google.cloud.logging.handlers.app_engine import _GAE_PROJECT_ENV_STANDARD
35-
from google.cloud.logging.handlers.app_engine import _GAE_SERVICE_ENV
36-
from google.cloud.logging.handlers.app_engine import _GAE_VERSION_ENV
32+
def test_constructor_w_gae_standard_env(self):
33+
import sys
34+
from google.cloud.logging.handlers import app_engine
3735

3836
client = mock.Mock(project=self.PROJECT, spec=["project"])
3937

@@ -42,35 +40,51 @@ def test_constructor(self):
4240
with mock.patch(
4341
"os.environ",
4442
new={
45-
_GAE_PROJECT_ENV_STANDARD: "test_project",
46-
_GAE_SERVICE_ENV: "test_service",
47-
_GAE_VERSION_ENV: "test_version",
43+
app_engine._GAE_PROJECT_ENV_STANDARD: "test_project",
44+
app_engine._GAE_SERVICE_ENV: "test_service",
45+
app_engine._GAE_VERSION_ENV: "test_version",
4846
},
4947
):
5048
handler = self._make_one(client, transport=_Transport)
49+
5150
self.assertIs(handler.client, client)
51+
self.assertEqual(handler.name, app_engine._DEFAULT_GAE_LOGGER_NAME)
5252
self.assertEqual(handler.resource.type, "gae_app")
5353
self.assertEqual(handler.resource.labels["project_id"], "test_project")
5454
self.assertEqual(handler.resource.labels["module_id"], "test_service")
5555
self.assertEqual(handler.resource.labels["version_id"], "test_version")
56+
self.assertIs(handler.stream, sys.stderr)
57+
58+
def test_constructor_w_gae_flex_env(self):
59+
import io
60+
from google.cloud.logging.handlers import app_engine
61+
62+
client = mock.Mock(project=self.PROJECT, spec=["project"])
63+
name = "test-logger"
64+
stream = io.BytesIO()
5665

5766
# Verify that _GAE_PROJECT_ENV_FLEX environment variable takes
5867
# precedence over _GAE_PROJECT_ENV_STANDARD.
5968
with mock.patch(
6069
"os.environ",
6170
new={
62-
_GAE_PROJECT_ENV_FLEX: "test_project_2",
63-
_GAE_PROJECT_ENV_STANDARD: "test_project_should_be_overridden",
64-
_GAE_SERVICE_ENV: "test_service_2",
65-
_GAE_VERSION_ENV: "test_version_2",
71+
app_engine._GAE_PROJECT_ENV_FLEX: "test_project_2",
72+
app_engine._GAE_PROJECT_ENV_STANDARD: "test_project_should_be_overridden",
73+
app_engine._GAE_SERVICE_ENV: "test_service_2",
74+
app_engine._GAE_VERSION_ENV: "test_version_2",
6675
},
6776
):
68-
handler = self._make_one(client, transport=_Transport)
77+
handler = self._make_one(
78+
client, name=name, transport=_Transport, stream=stream
79+
)
80+
6981
self.assertIs(handler.client, client)
82+
self.assertEqual(handler.name, name)
7083
self.assertEqual(handler.resource.type, "gae_app")
7184
self.assertEqual(handler.resource.labels["project_id"], "test_project_2")
7285
self.assertEqual(handler.resource.labels["module_id"], "test_service_2")
7386
self.assertEqual(handler.resource.labels["version_id"], "test_version_2")
87+
self.assertIs(handler.stream, stream)
7488

7589
def test_emit(self):
7690
client = mock.Mock(project=self.PROJECT, spec=["project"])

tests/unit/handlers/test_container_engine.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ def _get_target_class(self):
2828
def _make_one(self, *args, **kw):
2929
return self._get_target_class()(*args, **kw)
3030

31+
def test_ctor_defaults(self):
32+
handler = self._make_one()
33+
self.assertIsNone(handler.name)
34+
35+
def test_ctor_w_name(self):
36+
handler = self._make_one(name="foo")
37+
self.assertEqual(handler.name, "foo")
38+
3139
def test_format(self):
3240
import logging
3341
import json

tests/unit/handlers/test_handlers.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,47 @@ def _get_target_class():
2929
def _make_one(self, *args, **kw):
3030
return self._get_target_class()(*args, **kw)
3131

32-
def test_ctor(self):
32+
def test_ctor_defaults(self):
33+
import sys
34+
from google.cloud.logging.logger import _GLOBAL_RESOURCE
35+
from google.cloud.logging.handlers.handlers import DEFAULT_LOGGER_NAME
36+
3337
client = _Client(self.PROJECT)
3438
handler = self._make_one(client, transport=_Transport)
35-
self.assertEqual(handler.client, client)
39+
self.assertEqual(handler.name, DEFAULT_LOGGER_NAME)
40+
self.assertIs(handler.client, client)
41+
self.assertIsInstance(handler.transport, _Transport)
42+
self.assertIs(handler.transport.client, client)
43+
self.assertEqual(handler.transport.name, DEFAULT_LOGGER_NAME)
44+
self.assertIs(handler.resource, _GLOBAL_RESOURCE)
45+
self.assertIsNone(handler.labels)
46+
self.assertIs(handler.stream, sys.stderr)
47+
48+
def test_ctor_explicit(self):
49+
import io
50+
from google.cloud.logging.resource import Resource
51+
52+
resource = Resource("resource_type", {"resource_label": "value"})
53+
labels = {"handler_lable": "value"}
54+
name = "test-logger"
55+
client = _Client(self.PROJECT)
56+
stream = io.BytesIO()
57+
handler = self._make_one(
58+
client,
59+
name=name,
60+
transport=_Transport,
61+
resource=resource,
62+
labels=labels,
63+
stream=stream,
64+
)
65+
self.assertEqual(handler.name, name)
66+
self.assertIs(handler.client, client)
67+
self.assertIsInstance(handler.transport, _Transport)
68+
self.assertIs(handler.transport.client, client)
69+
self.assertEqual(handler.transport.name, name)
70+
self.assertIs(handler.resource, resource)
71+
self.assertEqual(handler.labels, labels)
72+
self.assertIs(handler.stream, stream)
3673

3774
def test_emit(self):
3875
from google.cloud.logging.logger import _GLOBAL_RESOURCE
@@ -108,7 +145,8 @@ def __init__(self, project):
108145

109146
class _Transport(object):
110147
def __init__(self, client, name):
111-
pass
148+
self.client = client
149+
self.name = name
112150

113151
def send(self, record, message, resource, labels=None):
114152
self.send_called_with = (record, message, resource, labels)

tests/unit/test_client.py

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -552,20 +552,23 @@ def test_get_default_handler_app_engine(self):
552552
from google.cloud.logging.handlers import AppEngineHandler
553553

554554
credentials = _make_credentials()
555+
client = self._make_one(
556+
project=self.PROJECT, credentials=credentials, _use_grpc=False
557+
)
555558

556559
with _Monkey(os, environ={_APPENGINE_FLEXIBLE_ENV_VM: "True"}):
557-
client = self._make_one(
558-
project=self.PROJECT, credentials=credentials, _use_grpc=False
559-
)
560560
handler = client.get_default_handler()
561561

562+
handler.transport.worker.stop()
563+
562564
self.assertIsInstance(handler, AppEngineHandler)
563565

564566
def test_get_default_handler_container_engine(self):
565567
from google.cloud.logging.handlers import ContainerEngineHandler
566568

569+
credentials = _make_credentials()
567570
client = self._make_one(
568-
project=self.PROJECT, credentials=_make_credentials(), _use_grpc=False
571+
project=self.PROJECT, credentials=credentials, _use_grpc=False
569572
)
570573

571574
patch = mock.patch(
@@ -579,29 +582,92 @@ def test_get_default_handler_container_engine(self):
579582
self.assertIsInstance(handler, ContainerEngineHandler)
580583

581584
def test_get_default_handler_general(self):
585+
import io
582586
from google.cloud.logging.handlers import CloudLoggingHandler
587+
from google.cloud.logging.resource import Resource
583588

584-
credentials = _make_credentials()
589+
name = "test-logger"
590+
resource = Resource("resource_type", {"resource_label": "value"})
591+
labels = {"handler_label": "value"}
592+
stream = io.BytesIO()
585593

594+
credentials = _make_credentials()
586595
client = self._make_one(
587596
project=self.PROJECT, credentials=credentials, _use_grpc=False
588597
)
589-
handler = client.get_default_handler()
598+
599+
handler = client.get_default_handler(
600+
name=name, resource=resource, labels=labels, stream=stream
601+
)
602+
603+
handler.transport.worker.stop()
590604

591605
self.assertIsInstance(handler, CloudLoggingHandler)
606+
self.assertEqual(handler.name, name)
607+
self.assertEqual(handler.resource, resource)
608+
self.assertEqual(handler.labels, labels)
592609

593610
def test_setup_logging(self):
594-
setup_logging = mock.Mock(spec=[])
611+
from google.cloud.logging.handlers import CloudLoggingHandler
595612

596613
credentials = _make_credentials()
614+
client = self._make_one(
615+
project=self.PROJECT, credentials=credentials, _use_grpc=False
616+
)
597617

598-
with mock.patch("google.cloud.logging.client.setup_logging", new=setup_logging):
599-
client = self._make_one(
600-
project=self.PROJECT, credentials=credentials, _use_grpc=False
601-
)
618+
with mock.patch("google.cloud.logging.client.setup_logging") as mocked:
602619
client.setup_logging()
603620

604-
setup_logging.assert_called()
621+
self.assertEqual(len(mocked.mock_calls), 1)
622+
_, args, kwargs = mocked.mock_calls[0]
623+
624+
handler, = args
625+
self.assertIsInstance(handler, CloudLoggingHandler)
626+
627+
handler.transport.worker.stop()
628+
629+
expected_kwargs = {
630+
"excluded_loggers": ("google.cloud", "google.auth", "google_auth_httplib2"),
631+
"log_level": 20,
632+
}
633+
self.assertEqual(kwargs, expected_kwargs)
634+
635+
def test_setup_logging_w_extra_kwargs(self):
636+
import io
637+
from google.cloud.logging.handlers import CloudLoggingHandler
638+
from google.cloud.logging.resource import Resource
639+
640+
name = "test-logger"
641+
resource = Resource("resource_type", {"resource_label": "value"})
642+
labels = {"handler_label": "value"}
643+
stream = io.BytesIO()
644+
645+
credentials = _make_credentials()
646+
client = self._make_one(
647+
project=self.PROJECT, credentials=credentials, _use_grpc=False
648+
)
649+
650+
with mock.patch("google.cloud.logging.client.setup_logging") as mocked:
651+
client.setup_logging(
652+
name=name, resource=resource, labels=labels, stream=stream
653+
)
654+
655+
self.assertEqual(len(mocked.mock_calls), 1)
656+
_, args, kwargs = mocked.mock_calls[0]
657+
658+
handler, = args
659+
self.assertIsInstance(handler, CloudLoggingHandler)
660+
self.assertEqual(handler.name, name)
661+
self.assertEqual(handler.resource, resource)
662+
self.assertEqual(handler.labels, labels)
663+
664+
handler.transport.worker.stop()
665+
666+
expected_kwargs = {
667+
"excluded_loggers": ("google.cloud", "google.auth", "google_auth_httplib2"),
668+
"log_level": 20,
669+
}
670+
self.assertEqual(kwargs, expected_kwargs)
605671

606672

607673
class _Connection(object):

0 commit comments

Comments
 (0)