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