Coverage for structlog_gcp/processors.py: 100%
37 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-13 18:00 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-13 18:00 +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
12class CoreCloudLogging:
13 """Initialize the Google Cloud Logging event message"""
15 def setup(self) -> list[Processor]:
16 return [
17 # If some value is in bytes, decode it to a unicode str.
18 structlog.processors.UnicodeDecoder(),
19 # Add a timestamp in ISO 8601 format.
20 structlog.processors.TimeStamper(fmt="iso"),
21 self,
22 ]
24 def __call__(
25 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
26 ) -> EventDict:
27 value = {
28 "message": event_dict.pop("event"),
29 "time": event_dict.pop("timestamp"),
30 }
32 event_dict[CLOUD_LOGGING_KEY] = value
33 return event_dict
36class FormatAsCloudLogging:
37 """Finalize the Google Cloud Logging event message and replace the logging event"""
39 def setup(self) -> list[Processor]:
40 return [self, structlog.processors.JSONRenderer()]
42 def __call__(
43 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
44 ) -> EventDict:
45 event: EventDict = event_dict.pop(CLOUD_LOGGING_KEY)
47 if event_dict:
48 event["logging.googleapis.com/labels"] = event_dict
50 return event
53class LogSeverity:
54 """Set the severity using the Google Cloud Logging severities"""
56 def __init__(self) -> None:
57 self.default = "notset"
59 # From Python's logging level to Google level
60 self.mapping = {
61 "notset": "DEFAULT", # The log entry has no assigned severity level.
62 "debug": "DEBUG", # Debug or trace information.
63 "info": "INFO", # Routine information, such as ongoing status or performance.
64 # "notice": "NOTICE", # Normal but significant events, such as start up, shut down, or a configuration change.
65 "warn": "WARNING", # Warning events might cause problems.
66 "warning": "WARNING", # Warning events might cause problems.
67 "error": "ERROR", # Error events are likely to cause problems.
68 "critical": "CRITICAL", # Critical events cause more severe problems or outages.
69 # "alert": "ALERT", # A person must take an action immediately.
70 # "emergency": "EMERGENCY", # One or more systems are unusable.
71 }
73 def setup(self) -> list[Processor]:
74 # Add log level to event dict.
75 return [structlog.processors.add_log_level, self]
77 def __call__(
78 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
79 ) -> EventDict:
80 """Format a Python log level value as a GCP log severity.
82 See: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
83 """
85 log_level = event_dict.pop("level")
86 severity = self.mapping.get(log_level, self.default)
88 event_dict[CLOUD_LOGGING_KEY]["severity"] = severity
89 return event_dict
92class CodeLocation:
93 """Inject the location of the logging message into the logs"""
95 def setup(self) -> list[Processor]:
96 # Add callsite parameters.
97 call_site_proc = structlog.processors.CallsiteParameterAdder(
98 parameters=[
99 structlog.processors.CallsiteParameter.PATHNAME,
100 structlog.processors.CallsiteParameter.MODULE,
101 structlog.processors.CallsiteParameter.FUNC_NAME,
102 structlog.processors.CallsiteParameter.LINENO,
103 ]
104 )
105 return [call_site_proc, self]
107 def __call__(
108 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
109 ) -> EventDict:
110 location = {
111 "file": event_dict.pop("pathname"),
112 "line": str(event_dict.pop("lineno")),
113 "function": f"{event_dict.pop('module')}:{event_dict.pop('func_name')}",
114 }
116 event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY] = location
118 return event_dict