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

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 

4 

5 

6import structlog.processors 

7from structlog.typing import EventDict, Processor, WrappedLogger 

8 

9from .types import CLOUD_LOGGING_KEY, SOURCE_LOCATION_KEY 

10 

11 

12def setup_log_severity() -> list[Processor]: 

13 return [structlog.processors.add_log_level, LogSeverity()] 

14 

15 

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 ) 

25 

26 return [call_site_processors, code_location] 

27 

28 

29def init_cloud_logging( 

30 logger: WrappedLogger, method_name: str, event_dict: EventDict 

31) -> EventDict: 

32 """Initialize the Google Cloud Logging event message""" 

33 

34 value = { 

35 "message": event_dict.pop("event"), 

36 "time": event_dict.pop("timestamp"), 

37 } 

38 

39 event_dict[CLOUD_LOGGING_KEY] = value 

40 return event_dict 

41 

42 

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. 

47 

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. 

51 

52 See: https://cloud.google.com/logging/docs/structured-logging#special-payload-fields 

53 """ 

54 

55 # Take out the Google Cloud Logging set of fields from the event dict 

56 gcp_event: EventDict = event_dict.pop(CLOUD_LOGGING_KEY) 

57 

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) 

61 

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 

67 

68 return event_dict 

69 

70 

71class LogSeverity: 

72 """Set the severity using the Google Cloud Logging severities. 

73 

74 

75 See: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity 

76 """ 

77 

78 def __init__(self) -> None: 

79 self.default = "notset" 

80 

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 } 

94 

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.""" 

99 

100 log_level = event_dict.pop("level") 

101 severity = self.mapping.get(log_level, self.default) 

102 

103 event_dict[CLOUD_LOGGING_KEY]["severity"] = severity 

104 return event_dict 

105 

106 

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""" 

111 

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 } 

117 

118 event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY] = location 

119 

120 return event_dict