utils/logging.py (41 lines of code) (raw):

# Copyright 2021 Google LLC # # 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. from typing import Dict from flask import request import structlog from utils import metadata def field_name_modifier( logger: structlog.PrintLogger, log_method: str, event_dict: Dict ) -> Dict: """Changes the keys for some of the fields, to match Cloud Logging's expectations https://cloud.google.com/run/docs/logging#special-fields """ # structlog example adapted from # https://github.com/ymotongpoo/cloud-logging-configurations/blob/master/python/structlog/main.py event_dict["severity"] = event_dict["level"] del event_dict["level"] if "event" in event_dict: event_dict["message"] = event_dict["event"] del event_dict["event"] return event_dict def trace_modifier( logger: structlog.PrintLogger, log_method: str, event_dict: Dict ) -> Dict: """Adds Tracing correlation https://cloud.google.com/run/docs/logging#correlate-logs """ # Only attempt to get the context if in a request if request: trace_header = request.headers.get("X-Cloud-Trace-Context") # Only append the trace if it exists in the request if trace_header: trace = trace_header.split("/") project = metadata.get_project_id() event_dict[ "logging.googleapis.com/trace" ] = f"projects/{project}/traces/{trace[0]}" return event_dict def getJSONLogger() -> structlog._config.BoundLoggerLazyProxy: """Create a JSON logger using the field name and trace modifiers created above""" # extend using https://www.structlog.org/en/stable/processors.html structlog.configure( processors=[ structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), field_name_modifier, trace_modifier, structlog.processors.TimeStamper("iso"), structlog.processors.JSONRenderer(), ], wrapper_class=structlog.stdlib.BoundLogger, ) return structlog.get_logger() logger = getJSONLogger() def flush() -> None: # Setting PYTHONUNBUFFERED in Dockerfile/Buildpack ensured no buffering # https://docs.python.org/3/library/logging.html#logging.shutdown # When the logging module is imported, it registers this # function as an exit handler (see atexit), so normally # there’s no need to do that manually. pass