Coverage for structlog_gcp/processors.py: 100%
29 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-21 14:36 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-21 14:36 +0000
1# https://cloud.google.com/functions/docs/monitoring/logging#writing_structured_logs
2# https://cloud.google.com/logging/docs/agent/logging/configuration#process-payload
3# https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
6import structlog.processors
7from structlog.typing import EventDict, Processor, WrappedLogger
9from .types import CLOUD_LOGGING_KEY, SOURCE_LOCATION_KEY
12def setup_log_severity() -> list[Processor]:
13 return [structlog.processors.add_log_level, LogSeverity()]
16def setup_code_location() -> list[Processor]:
17 call_site_processors = structlog.processors.CallsiteParameterAdder(
18 parameters=[
19 structlog.processors.CallsiteParameter.PATHNAME,
20 structlog.processors.CallsiteParameter.MODULE,
21 structlog.processors.CallsiteParameter.FUNC_NAME,
22 structlog.processors.CallsiteParameter.LINENO,
23 ]
24 )
26 return [call_site_processors, code_location]
29def init_cloud_logging(
30 logger: WrappedLogger, method_name: str, event_dict: EventDict
31) -> EventDict:
32 """Initialize the Google Cloud Logging event message"""
34 value = {
35 "message": event_dict.pop("event"),
36 "time": event_dict.pop("timestamp"),
37 }
39 event_dict[CLOUD_LOGGING_KEY] = value
40 return event_dict
43def finalize_cloud_logging(
44 logger: WrappedLogger, method_name: str, event_dict: EventDict
45) -> EventDict:
46 """Finalize the Google Cloud Logging event message and replace the logging event.
48 This is not exactly the format the Cloud Logging directly ingests, but
49 Cloud Logging is smart enough to transform basic JSON-like logging events
50 into Cloud Logging-compatible events.
52 See: https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
53 """
55 # Take out the Google Cloud Logging set of fields from the event dict
56 gcp_event: EventDict = event_dict.pop(CLOUD_LOGGING_KEY)
58 # Override whatever is left from the event dict with the content of all
59 # the Google Cloud Logging-formatted fields.
60 event_dict.update(gcp_event)
62 # Fields which are not known by Google Cloud Logging will be added to
63 # the `jsonPayload` field.
64 #
65 # See the `message` field documentation in:
66 # https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
68 return event_dict
71class LogSeverity:
72 """Set the severity using the Google Cloud Logging severities.
75 See: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
76 """
78 def __init__(self) -> None:
79 self.default = "notset"
81 # From Python's logging level to Google level
82 self.mapping = {
83 "notset": "DEFAULT", # The log entry has no assigned severity level.
84 "debug": "DEBUG", # Debug or trace information.
85 "info": "INFO", # Routine information, such as ongoing status or performance.
86 # "notice": "NOTICE", # Normal but significant events, such as start up, shut down, or a configuration change.
87 "warn": "WARNING", # Warning events might cause problems.
88 "warning": "WARNING", # Warning events might cause problems.
89 "error": "ERROR", # Error events are likely to cause problems.
90 "critical": "CRITICAL", # Critical events cause more severe problems or outages.
91 # "alert": "ALERT", # A person must take an action immediately.
92 # "emergency": "EMERGENCY", # One or more systems are unusable.
93 }
95 def __call__(
96 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
97 ) -> EventDict:
98 """Format a Python log level value as a GCP log severity."""
100 log_level = event_dict.pop("level")
101 severity = self.mapping.get(log_level, self.default)
103 event_dict[CLOUD_LOGGING_KEY]["severity"] = severity
104 return event_dict
107def code_location(
108 logger: WrappedLogger, method_name: str, event_dict: EventDict
109) -> EventDict:
110 """Inject the location of the logging message into the logs"""
112 location = {
113 "file": event_dict.pop("pathname"),
114 "line": str(event_dict.pop("lineno")),
115 "function": f"{event_dict.pop('module')}:{event_dict.pop('func_name')}",
116 }
118 event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY] = location
120 return event_dict