diff --git a/docs/handlers-app-engine.rst b/docs/handlers-app-engine.rst deleted file mode 100644 index f25223a20..000000000 --- a/docs/handlers-app-engine.rst +++ /dev/null @@ -1,6 +0,0 @@ -Google App Engine flexible Log Handler -====================================== - -.. automodule:: google.cloud.logging_v2.handlers.app_engine - :members: - :show-inheritance: diff --git a/docs/handlers-container-engine.rst b/docs/handlers-container-engine.rst deleted file mode 100644 index 981b41dcb..000000000 --- a/docs/handlers-container-engine.rst +++ /dev/null @@ -1,6 +0,0 @@ -Google Kubernetes Engine Log Handler -==================================== - -.. automodule:: google.cloud.logging_v2.handlers.container_engine - :members: - :show-inheritance: diff --git a/docs/handlers-structured-log.rst b/docs/handlers-structured-log.rst new file mode 100644 index 000000000..77e9c0c84 --- /dev/null +++ b/docs/handlers-structured-log.rst @@ -0,0 +1,6 @@ +Structured Log Handler +==================================== + +.. automodule:: google.cloud.logging_v2.handlers.structured_log + :members: + :show-inheritance: diff --git a/docs/usage.rst b/docs/usage.rst index 1ea9440fc..037a41b55 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -327,19 +327,14 @@ logging handler can use different transports. The default is fluentd logging handlers ~~~~~~~~~~~~~~~~~~~~~~~~ -Besides :class:`~google.cloud.logging.handlers.handlers.CloudLoggingHandler`, -which writes directly to the API, two other handlers are provided. -:class:`~google.cloud.logging.handlers.app_engine.AppEngineHandler`, which is -recommended when running on the Google App Engine Flexible vanilla runtimes -(i.e. your app.yaml contains ``runtime: python``), and -:class:`~google.cloud.logging.handlers.container_engine.ContainerEngineHandler` -, which is recommended when running on `Google Kubernetes Engine`_ with the -Cloud Logging plugin enabled. +Two handler classes are provided: :class:`~google.cloud.logging.handlers.handlers.CloudLoggingHandler`, +which writes directly to the API, and :class:`~google.cloud.logging.handlers.structured_log.StructuredLogHandler`, +which writes logs as structured JSON to standard output (to be later parsed by the environment). :meth:`~google.cloud.logging.client.Client.get_default_handler` and :meth:`~google.cloud.logging.client.Client.setup_logging` will attempt to use -the environment to automatically detect whether the code is running in -these platforms and use the appropriate handler. +the environment to automatically detect where the code is running and use the +appropriate handler. In both cases, the fluentd agent is configured to automatically parse log files in an expected format and forward them to Cloud Logging. The handlers diff --git a/docs/v2.rst b/docs/v2.rst index 823097bd7..78fa01097 100644 --- a/docs/v2.rst +++ b/docs/v2.rst @@ -12,8 +12,7 @@ v2 sink stdlib-usage handlers - handlers-app-engine - handlers-container-engine + handlers-structured-log transports-sync transports-thread transports-base diff --git a/google/cloud/logging/handlers/__init__.py b/google/cloud/logging/handlers/__init__.py index e27f8e673..ae3db9cc6 100644 --- a/google/cloud/logging/handlers/__init__.py +++ b/google/cloud/logging/handlers/__init__.py @@ -14,18 +14,14 @@ """Python :mod:`logging` handlers for Google Cloud Logging.""" -from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler -from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler from google.cloud.logging_v2.handlers.handlers import setup_logging __all__ = [ - "AppEngineHandler", "CloudLoggingFilter", "CloudLoggingHandler", - "ContainerEngineHandler", "StructuredLogHandler", "setup_logging", ] diff --git a/google/cloud/logging_v2/client.py b/google/cloud/logging_v2/client.py index 51d93355c..c169d112d 100644 --- a/google/cloud/logging_v2/client.py +++ b/google/cloud/logging_v2/client.py @@ -35,8 +35,6 @@ from google.cloud.logging_v2._http import _MetricsAPI as JSONMetricsAPI from google.cloud.logging_v2._http import _SinksAPI as JSONSinksAPI from google.cloud.logging_v2.handlers import CloudLoggingHandler -from google.cloud.logging_v2.handlers import AppEngineHandler -from google.cloud.logging_v2.handlers import ContainerEngineHandler from google.cloud.logging_v2.handlers import StructuredLogHandler from google.cloud.logging_v2.handlers import setup_logging from google.cloud.logging_v2.handlers.handlers import EXCLUDED_LOGGER_DEFAULTS @@ -352,9 +350,9 @@ def get_default_handler(self, **kw): if isinstance(monitored_resource, Resource): if monitored_resource.type == _GAE_RESOURCE_TYPE: - return AppEngineHandler(self, **kw) + CloudLoggingHandler(self, resource=monitored_resource, **kw) elif monitored_resource.type == _GKE_RESOURCE_TYPE: - return ContainerEngineHandler(**kw) + return StructuredLogHandler(**kw, project_id=self.project) elif ( monitored_resource.type == _GCF_RESOURCE_TYPE and sys.version_info[0] == 3 diff --git a/google/cloud/logging_v2/handlers/__init__.py b/google/cloud/logging_v2/handlers/__init__.py index a1ed08b5e..7e59247dc 100644 --- a/google/cloud/logging_v2/handlers/__init__.py +++ b/google/cloud/logging_v2/handlers/__init__.py @@ -14,18 +14,14 @@ """Python :mod:`logging` handlers for Google Cloud Logging.""" -from google.cloud.logging_v2.handlers.app_engine import AppEngineHandler -from google.cloud.logging_v2.handlers.container_engine import ContainerEngineHandler from google.cloud.logging_v2.handlers.structured_log import StructuredLogHandler from google.cloud.logging_v2.handlers.handlers import CloudLoggingHandler from google.cloud.logging_v2.handlers.handlers import CloudLoggingFilter from google.cloud.logging_v2.handlers.handlers import setup_logging __all__ = [ - "AppEngineHandler", "CloudLoggingFilter", "CloudLoggingHandler", - "ContainerEngineHandler", "StructuredLogHandler", "setup_logging", ] diff --git a/google/cloud/logging_v2/handlers/_helpers.py b/google/cloud/logging_v2/handlers/_helpers.py index 931b7a2f5..ce7da3bd9 100644 --- a/google/cloud/logging_v2/handlers/_helpers.py +++ b/google/cloud/logging_v2/handlers/_helpers.py @@ -14,8 +14,6 @@ """Helper functions for logging handlers.""" -import math -import json import re try: @@ -34,24 +32,6 @@ _PROTOCOL_HEADER = "SERVER_PROTOCOL" -def format_stackdriver_json(record, message): - """Helper to format a LogRecord in in Stackdriver fluentd format. - - Returns: - str: JSON str to be written to the log file. - """ - subsecond, second = math.modf(record.created) - - payload = { - "message": message, - "timestamp": {"seconds": int(second), "nanos": int(subsecond * 1e9)}, - "thread": record.thread, - "severity": record.levelname, - } - - return json.dumps(payload, ensure_ascii=False) - - def get_request_data_from_flask(): """Get http_request and trace data from flask request headers. @@ -68,10 +48,7 @@ def get_request_data_from_flask(): http_request = { "requestMethod": flask.request.method, "requestUrl": flask.request.url, - "requestSize": flask.request.content_length, "userAgent": flask.request.user_agent.string, - "remoteIp": flask.request.remote_addr, - "referer": flask.request.referrer, "protocol": flask.request.environ.get(_PROTOCOL_HEADER), } @@ -96,21 +73,11 @@ def get_request_data_from_django(): if request is None: return None, None, None - # convert content_length to int if it exists - content_length = None - try: - content_length = int(request.META.get(_DJANGO_CONTENT_LENGTH)) - except (ValueError, TypeError): - content_length = None - # build http_request http_request = { "requestMethod": request.method, "requestUrl": request.build_absolute_uri(), - "requestSize": content_length, "userAgent": request.META.get(_DJANGO_USERAGENT_HEADER), - "remoteIp": request.META.get(_DJANGO_REMOTE_ADDR_HEADER), - "referer": request.META.get(_DJANGO_REFERER_HEADER), "protocol": request.META.get(_PROTOCOL_HEADER), } diff --git a/google/cloud/logging_v2/handlers/app_engine.py b/google/cloud/logging_v2/handlers/app_engine.py deleted file mode 100644 index 874a9d608..000000000 --- a/google/cloud/logging_v2/handlers/app_engine.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2016 Google LLC All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Logging handler for App Engine Flexible - -Sends logs to the Cloud Logging API with the appropriate resource -and labels for App Engine logs. -""" - -import logging -import os - -from google.cloud.logging_v2.handlers._helpers import get_request_data -from google.cloud.logging_v2.handlers._monitored_resources import ( - _create_app_engine_resource, -) -from google.cloud.logging_v2.handlers.transports import BackgroundThreadTransport - -_DEFAULT_GAE_LOGGER_NAME = "app" - -_GAE_PROJECT_ENV_FLEX = "GCLOUD_PROJECT" -_GAE_PROJECT_ENV_STANDARD = "GOOGLE_CLOUD_PROJECT" -_GAE_SERVICE_ENV = "GAE_SERVICE" -_GAE_VERSION_ENV = "GAE_VERSION" - -_TRACE_ID_LABEL = "appengine.googleapis.com/trace_id" - - -class AppEngineHandler(logging.StreamHandler): - """A logging handler that sends App Engine-formatted logs to Stackdriver.""" - - def __init__( - self, - client, - *, - name=_DEFAULT_GAE_LOGGER_NAME, - transport=BackgroundThreadTransport, - stream=None, - ): - """ - Args: - client (~logging_v2.client.Client): The authenticated - Google Cloud Logging client for this handler to use. - name (Optional[str]): Name for the logger. - transport (Optional[~logging_v2.transports.Transport]): - The transport class. It should be a subclass - of :class:`.Transport`. If unspecified, - :class:`.BackgroundThreadTransport` will be used. - stream (Optional[IO]): Stream to be used by the handler. - - """ - super(AppEngineHandler, self).__init__(stream) - self.name = name - self.client = client - self.transport = transport(client, name) - self.project_id = os.environ.get( - _GAE_PROJECT_ENV_FLEX, os.environ.get(_GAE_PROJECT_ENV_STANDARD, "") - ) - self.module_id = os.environ.get(_GAE_SERVICE_ENV, "") - self.version_id = os.environ.get(_GAE_VERSION_ENV, "") - self.resource = self.get_gae_resource() - - def get_gae_resource(self): - """Return the GAE resource using the environment variables. - - Returns: - google.cloud.logging_v2.resource.Resource: Monitored resource for GAE. - """ - return _create_app_engine_resource() - - def get_gae_labels(self): - """Return the labels for GAE app. - - If the trace ID can be detected, it will be included as a label. - Currently, no other labels are included. - - Returns: - dict: Labels for GAE app. - """ - gae_labels = {} - - _, trace_id, _ = get_request_data() - if trace_id is not None: - gae_labels[_TRACE_ID_LABEL] = trace_id - - return gae_labels - - def emit(self, record): - """Actually log the specified logging record. - - Overrides the default emit behavior of ``StreamHandler``. - - See https://docs.python.org/2/library/logging.html#handler-objects - - Args: - record (logging.LogRecord): The record to be logged. - """ - message = super(AppEngineHandler, self).format(record) - inferred_http, inferred_trace, _ = get_request_data() - if inferred_trace is not None: - inferred_trace = f"projects/{self.project_id}/traces/{inferred_trace}" - # allow user overrides - trace = getattr(record, "trace", inferred_trace) - span_id = getattr(record, "span_id", None) - http_request = getattr(record, "http_request", inferred_http) - resource = getattr(record, "resource", self.resource) - user_labels = getattr(record, "labels", {}) - # merge labels - gae_labels = self.get_gae_labels() - gae_labels.update(user_labels) - # send off request - self.transport.send( - record, - message, - resource=resource, - labels=gae_labels, - trace=trace, - span_id=span_id, - http_request=http_request, - ) diff --git a/google/cloud/logging_v2/handlers/container_engine.py b/google/cloud/logging_v2/handlers/container_engine.py deleted file mode 100644 index a4bd0f848..000000000 --- a/google/cloud/logging_v2/handlers/container_engine.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2016 Google LLC All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""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 -metadata such as log level is properly captured. -""" - -import logging.handlers - -from google.cloud.logging_v2.handlers._helpers import format_stackdriver_json - - -class ContainerEngineHandler(logging.StreamHandler): - """Handler to format log messages the format expected by GKE fluent. - - This handler is written to format messages for the Google Container Engine - (GKE) fluentd plugin, so that metadata such as log level are properly set. - """ - - def __init__(self, *, name=None, stream=None): - """ - Args: - name (Optional[str]): The name of the custom log in Cloud Logging. - stream (Optional[IO]): Stream to be used by the handler. - - """ - super(ContainerEngineHandler, self).__init__(stream=stream) - self.name = name - - def format(self, record): - """Format the message into JSON expected by fluentd. - - Args: - record (logging.LogRecord): The log record. - - Returns: - str: A JSON string formatted for GKE fluentd. - """ - message = super(ContainerEngineHandler, self).format(record) - return format_stackdriver_json(record, message) diff --git a/google/cloud/logging_v2/handlers/handlers.py b/google/cloud/logging_v2/handlers/handlers.py index b3b787fe2..ad8a345a9 100644 --- a/google/cloud/logging_v2/handlers/handlers.py +++ b/google/cloud/logging_v2/handlers/handlers.py @@ -33,8 +33,15 @@ "werkzeug", ) +"""These environments require us to remove extra handlers on setup""" _CLEAR_HANDLER_RESOURCE_TYPES = ("gae_app", "cloud_function") +"""Extra trace label to be added on App Engine environments""" +_GAE_TRACE_ID_LABEL = "appengine.googleapis.com/trace_id" + +"""Resource name for App Engine environments""" +_GAE_RESOURCE_TYPE = "gae_app" + class CloudLoggingFilter(logging.Filter): """Python standard ``logging`` Filter class to add Cloud Logging @@ -45,10 +52,6 @@ class CloudLoggingFilter(logging.Filter): overwritten using the `extras` argument when writing logs. """ - # The subset of http_request fields have been tested to work consistently across GCP environments - # https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#httprequest - _supported_http_fields = ("requestMethod", "requestUrl", "userAgent", "protocol") - def __init__(self, project=None, default_labels=None): self.project = project self.default_labels = default_labels if default_labels else {} @@ -80,13 +83,6 @@ def filter(self, record): user_labels = getattr(record, "labels", {}) # infer request data from the environment inferred_http, inferred_trace, inferred_span = get_request_data() - if inferred_http is not None: - # filter inferred_http to include only well-supported fields - inferred_http = { - k: v - for (k, v) in inferred_http.items() - if k in self._supported_http_fields and v is not None - } if inferred_trace is not None and self.project is not None: # add full path for detected trace inferred_trace = f"projects/{self.project}/traces/{inferred_trace}" @@ -146,6 +142,7 @@ def __init__( resource=_GLOBAL_RESOURCE, labels=None, stream=None, + include_gae_labels=False, ): """ Args: @@ -188,12 +185,17 @@ def emit(self, record): record (logging.LogRecord): The record to be logged. """ message = super(CloudLoggingHandler, self).format(record) + labels = record._labels + resource = record._resource or self.resource + if resource.type == _GAE_RESOURCE_TYPE and record._trace is not None: + # add GAE-specific label + labels = {_GAE_TRACE_ID_LABEL: record._trace, **(labels or {})} # send off request self.transport.send( record, message, - resource=(record._resource or self.resource), - labels=record._labels, + resource=resource, + labels=labels, trace=record._trace, span_id=record._span_id, http_request=record._http_request, diff --git a/tests/environment b/tests/environment index 30d6a8083..834e5f4eb 160000 --- a/tests/environment +++ b/tests/environment @@ -1 +1 @@ -Subproject commit 30d6a80838a1cae6fb3945f41f3e1d90e815c0c9 +Subproject commit 834e5f4ebd76e2dbabb9237a54f9184964dcda1a diff --git a/tests/system/test_system.py b/tests/system/test_system.py index cc6d03804..fb9fd6692 100644 --- a/tests/system/test_system.py +++ b/tests/system/test_system.py @@ -30,7 +30,6 @@ from google.api_core.exceptions import ServiceUnavailable import google.cloud.logging from google.cloud._helpers import UTC -from google.cloud.logging_v2.handlers import AppEngineHandler from google.cloud.logging_v2.handlers import CloudLoggingHandler from google.cloud.logging_v2.handlers.transports import SyncTransport from google.cloud.logging_v2 import client @@ -320,37 +319,38 @@ def test_log_handler_sync(self): def test_handlers_w_extras(self): LOG_MESSAGE = "Testing with injected extras." - for cls in [CloudLoggingHandler, AppEngineHandler]: - LOGGER_NAME = f"{cls.__name__}-handler_extras" - handler_name = self._logger_name(LOGGER_NAME) - - handler = cls(Config.CLIENT, name=handler_name, transport=SyncTransport) - - # only create the logger to delete, hidden otherwise - logger = Config.CLIENT.logger(handler.name) - self.to_delete.append(logger) - - cloud_logger = logging.getLogger(LOGGER_NAME) - cloud_logger.addHandler(handler) - expected_request = {"requestUrl": "localhost"} - expected_source = {"file": "test.py"} - extra = { - "trace": "123", - "span_id": "456", - "http_request": expected_request, - "source_location": expected_source, - "resource": Resource(type="cloudiot_device", labels={}), - "labels": {"test-label": "manual"}, - } - cloud_logger.warn(LOG_MESSAGE, extra=extra) - - entries = _list_entries(logger) - self.assertEqual(len(entries), 1) - self.assertEqual(entries[0].trace, extra["trace"]) - self.assertEqual(entries[0].span_id, extra["span_id"]) - self.assertEqual(entries[0].http_request, expected_request) - self.assertEqual(entries[0].labels, extra["labels"]) - self.assertEqual(entries[0].resource.type, extra["resource"].type) + LOGGER_NAME = "handler_extras" + handler_name = self._logger_name(LOGGER_NAME) + + handler = CloudLoggingHandler( + Config.CLIENT, name=handler_name, transport=SyncTransport + ) + + # only create the logger to delete, hidden otherwise + logger = Config.CLIENT.logger(handler.name) + self.to_delete.append(logger) + + cloud_logger = logging.getLogger(LOGGER_NAME) + cloud_logger.addHandler(handler) + expected_request = {"requestUrl": "localhost"} + expected_source = {"file": "test.py"} + extra = { + "trace": "123", + "span_id": "456", + "http_request": expected_request, + "source_location": expected_source, + "resource": Resource(type="cloudiot_device", labels={}), + "labels": {"test-label": "manual"}, + } + cloud_logger.warn(LOG_MESSAGE, extra=extra) + + entries = _list_entries(logger) + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0].trace, extra["trace"]) + self.assertEqual(entries[0].span_id, extra["span_id"]) + self.assertEqual(entries[0].http_request, expected_request) + self.assertEqual(entries[0].labels, extra["labels"]) + self.assertEqual(entries[0].resource.type, extra["resource"].type) def test_log_root_handler(self): LOG_MESSAGE = "It was the best of times." diff --git a/tests/unit/handlers/test_app_engine.py b/tests/unit/handlers/test_app_engine.py deleted file mode 100644 index c726c8496..000000000 --- a/tests/unit/handlers/test_app_engine.py +++ /dev/null @@ -1,223 +0,0 @@ -# Copyright 2016 Google LLC All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import unittest - -import mock - - -class TestAppEngineHandler(unittest.TestCase): - PROJECT = "PROJECT" - - def _get_target_class(self): - from google.cloud.logging.handlers import AppEngineHandler - - return AppEngineHandler - - def _make_one(self, *args, **kw): - return self._get_target_class()(*args, **kw) - - def test_constructor_w_gae_standard_env(self): - import sys - from google.cloud.logging_v2.handlers import app_engine - - client = mock.Mock(project=self.PROJECT, spec=["project"]) - - # Verify that project/service/version are picked up from the - # environment. - with mock.patch( - "os.environ", - new={ - app_engine._GAE_SERVICE_ENV: "test_service", - app_engine._GAE_VERSION_ENV: "test_version", - }, - ), mock.patch( - "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", - return_value=self.PROJECT, - ): - handler = self._make_one(client, transport=_Transport) - - self.assertIs(handler.client, client) - self.assertEqual(handler.name, app_engine._DEFAULT_GAE_LOGGER_NAME) - self.assertEqual(handler.resource.type, "gae_app") - self.assertEqual(handler.resource.labels["project_id"], self.PROJECT) - self.assertEqual(handler.resource.labels["module_id"], "test_service") - self.assertEqual(handler.resource.labels["version_id"], "test_version") - self.assertIs(handler.stream, sys.stderr) - - def test_constructor_w_gae_flex_env(self): - import io - from google.cloud.logging_v2.handlers import app_engine - - client = mock.Mock(project=self.PROJECT, spec=["project"]) - name = "test-logger" - stream = io.BytesIO() - - # Verify that _GAE_PROJECT_ENV_FLEX environment variable takes - # precedence over _GAE_PROJECT_ENV_STANDARD. - with mock.patch( - "os.environ", - new={ - app_engine._GAE_PROJECT_ENV_FLEX: "test_project_2", - app_engine._GAE_PROJECT_ENV_STANDARD: "test_project_should_be_overridden", - app_engine._GAE_SERVICE_ENV: "test_service_2", - app_engine._GAE_VERSION_ENV: "test_version_2", - }, - ), mock.patch( - "google.cloud.logging_v2.handlers._monitored_resources.retrieve_metadata_server", - return_value=self.PROJECT, - ): - handler = self._make_one( - client, name=name, transport=_Transport, stream=stream - ) - - self.assertIs(handler.client, client) - self.assertEqual(handler.name, name) - self.assertEqual(handler.resource.type, "gae_app") - self.assertEqual(handler.resource.labels["project_id"], self.PROJECT) - self.assertEqual(handler.resource.labels["module_id"], "test_service_2") - self.assertEqual(handler.resource.labels["version_id"], "test_version_2") - self.assertIs(handler.stream, stream) - - def test_emit(self): - expected_http_request = {"request_url": "test"} - trace_id = "trace-test" - expected_trace_id = f"projects/{self.PROJECT}/traces/{trace_id}" - get_request_patch = mock.patch( - "google.cloud.logging_v2.handlers.app_engine.get_request_data", - return_value=(expected_http_request, trace_id, None), - ) - with get_request_patch: - # library integrations mocked to return test data - client = mock.Mock(project=self.PROJECT, spec=["project"]) - handler = self._make_one(client, transport=_Transport) - gae_resource = handler.get_gae_resource() - gae_labels = handler.get_gae_labels() - logname = "app" - message = "hello world" - record = logging.LogRecord( - logname, logging, None, None, message, None, None - ) - handler.project_id = self.PROJECT - handler.emit(record) - - self.assertIs(handler.transport.client, client) - self.assertEqual(handler.transport.name, logname) - self.assertEqual( - handler.transport.send_called_with, - ( - record, - message, - gae_resource, - gae_labels, - expected_trace_id, - None, - expected_http_request, - ), - ) - - def test_emit_manual_field_override(self): - from google.cloud.logging_v2.resource import Resource - - inferred_http_request = {"request_url": "test"} - inferred_trace_id = "trace-test" - get_request_patch = mock.patch( - "google.cloud.logging_v2.handlers.app_engine.get_request_data", - return_value=(inferred_http_request, inferred_trace_id, None), - ) - with get_request_patch: - # library integrations mocked to return test data - client = mock.Mock(project=self.PROJECT, spec=["project"]) - handler = self._make_one(client, transport=_Transport) - gae_labels = handler.get_gae_labels() - logname = "app" - message = "hello world" - record = logging.LogRecord( - logname, logging, None, None, message, None, None - ) - handler.project_id = self.PROJECT - # set attributes manually - expected_trace = "123" - setattr(record, "trace", expected_trace) - expected_span = "456" - setattr(record, "span_id", expected_span) - expected_http = {"reuqest_url": "manual"} - setattr(record, "http_request", expected_http) - expected_resource = Resource(type="test", labels={}) - setattr(record, "resource", expected_resource) - additional_labels = {"test-label": "manual"} - expected_labels = dict(gae_labels) - expected_labels.update(additional_labels) - setattr(record, "labels", additional_labels) - handler.emit(record) - self.assertIs(handler.transport.client, client) - self.assertEqual(handler.transport.name, logname) - self.assertEqual( - handler.transport.send_called_with, - ( - record, - message, - expected_resource, - expected_labels, - expected_trace, - expected_span, - expected_http, - ), - ) - - def _get_gae_labels_helper(self, trace_id): - get_request_patch = mock.patch( - "google.cloud.logging_v2.handlers.app_engine.get_request_data", - return_value=(None, trace_id, None), - ) - - client = mock.Mock(project=self.PROJECT, spec=["project"]) - # The handler actually calls ``get_gae_labels()``. - with get_request_patch as mock_get_request: - handler = self._make_one(client, transport=_Transport) - - gae_labels = handler.get_gae_labels() - self.assertEqual(mock_get_request.mock_calls, [mock.call()]) - - return gae_labels - - def test_get_gae_labels_with_label(self): - from google.cloud.logging_v2.handlers import app_engine - - trace_id = "test-gae-trace-id" - gae_labels = self._get_gae_labels_helper(trace_id) - expected_labels = {app_engine._TRACE_ID_LABEL: trace_id} - self.assertEqual(gae_labels, expected_labels) - - def test_get_gae_labels_without_label(self): - gae_labels = self._get_gae_labels_helper(None) - self.assertEqual(gae_labels, {}) - - -class _Transport(object): - def __init__(self, client, name): - self.client = client - self.name = name - - def send(self, record, message, resource, labels, trace, span_id, http_request): - self.send_called_with = ( - record, - message, - resource, - labels, - trace, - span_id, - http_request, - ) diff --git a/tests/unit/handlers/test_container_engine.py b/tests/unit/handlers/test_container_engine.py deleted file mode 100644 index 280ab9cf0..000000000 --- a/tests/unit/handlers/test_container_engine.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright 2016 Google LLC All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - - -class TestContainerEngineHandler(unittest.TestCase): - PROJECT = "PROJECT" - - def _get_target_class(self): - from google.cloud.logging.handlers import ContainerEngineHandler - - return ContainerEngineHandler - - def _make_one(self, *args, **kw): - return self._get_target_class()(*args, **kw) - - def test_ctor_defaults(self): - handler = self._make_one() - self.assertIsNone(handler.name) - - def test_ctor_w_name(self): - handler = self._make_one(name="foo") - self.assertEqual(handler.name, "foo") - - def test_format(self): - import logging - import json - - handler = self._make_one() - logname = "loggername" - message = "hello world,嗨 世界" - record = logging.LogRecord( - logname, logging.INFO, None, None, message, None, None - ) - record.created = 5.03 - expected_payload = { - "message": message, - "timestamp": {"seconds": 5, "nanos": int(0.03 * 1e9)}, - "thread": record.thread, - "severity": record.levelname, - } - payload = handler.format(record) - - self.assertEqual(payload, json.dumps(expected_payload, ensure_ascii=False)) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 9dbfa87fd..11ccd7e37 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -719,7 +719,7 @@ def test_get_default_handler_app_engine(self): import os from google.cloud._testing import _Monkey from google.cloud.logging_v2.handlers._monitored_resources import _GAE_ENV_VARS - from google.cloud.logging.handlers import AppEngineHandler + from google.cloud.logging.handlers import CloudLoggingHandler credentials = _make_credentials() client = self._make_one( @@ -733,10 +733,10 @@ def test_get_default_handler_app_engine(self): handler.transport.worker.stop() - self.assertIsInstance(handler, AppEngineHandler) + self.assertIsInstance(handler, CloudLoggingHandler) def test_get_default_handler_container_engine(self): - from google.cloud.logging.handlers import ContainerEngineHandler + from google.cloud.logging.handlers import StructuredLogHandler credentials = _make_credentials() client = self._make_one( @@ -751,7 +751,7 @@ def test_get_default_handler_container_engine(self): with patch: handler = client.get_default_handler() - self.assertIsInstance(handler, ContainerEngineHandler) + self.assertIsInstance(handler, StructuredLogHandler) def test_get_default_handler_general(self): import io