Coverage for structlog_gcp/processors.py: 100%

36 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-04-02 12:16 +0200

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 

5import structlog.processors 

6 

7from .types import CLOUD_LOGGING_KEY, SOURCE_LOCATION_KEY 

8 

9 

10class CoreCloudLogging: 

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

12 

13 def setup(self): 

14 return [ 

15 # If some value is in bytes, decode it to a unicode str. 

16 structlog.processors.UnicodeDecoder(), 

17 # Add a timestamp in ISO 8601 format. 

18 structlog.processors.TimeStamper(fmt="iso"), 

19 self.__call__, 

20 ] 

21 

22 def __call__(self, logger, method_name, event_dict): 

23 value = { 

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

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

26 } 

27 

28 event_dict[CLOUD_LOGGING_KEY] = value 

29 return event_dict 

30 

31 

32class FormatAsCloudLogging: 

33 """Finalize the Google Cloud Logging event message and replace the logging event""" 

34 

35 def setup(self): 

36 return [ 

37 self.__call__, 

38 structlog.processors.JSONRenderer(), 

39 ] 

40 

41 def __call__(self, logger, method_name, event_dict): 

42 event = event_dict.pop(CLOUD_LOGGING_KEY) 

43 

44 if event_dict: 

45 event["logging.googleapis.com/labels"] = event_dict 

46 

47 return event 

48 

49 

50class LogSeverity: 

51 """Set the severity using the Google Cloud Logging severities""" 

52 

53 def __init__(self): 

54 self.default = "notset" 

55 

56 # From Python's logging level to Google level 

57 self.mapping = { 

58 "notset": "DEFAULT", # The log entry has no assigned severity level. 

59 "debug": "DEBUG", # Debug or trace information. 

60 "info": "INFO", # Routine information, such as ongoing status or performance. 

61 # "notice": "NOTICE", # Normal but significant events, such as start up, shut down, or a configuration change. 

62 "warn": "WARNING", # Warning events might cause problems. 

63 "warning": "WARNING", # Warning events might cause problems. 

64 "error": "ERROR", # Error events are likely to cause problems. 

65 "critical": "CRITICAL", # Critical events cause more severe problems or outages. 

66 # "alert": "ALERT", # A person must take an action immediately. 

67 # "emergency": "EMERGENCY", # One or more systems are unusable. 

68 } 

69 

70 def setup(self): 

71 return [ 

72 # Add log level to event dict. 

73 structlog.processors.add_log_level, 

74 self.__call__, 

75 ] 

76 

77 def __call__(self, logger, method_name, event_dict): 

78 """Format a Python log level value as a GCP log severity. 

79 

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

81 """ 

82 

83 log_level = event_dict.pop("level") 

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

85 

86 event_dict[CLOUD_LOGGING_KEY]["severity"] = severity 

87 return event_dict 

88 

89 

90class CodeLocation: 

91 """Inject the location of the logging message into the logs""" 

92 

93 def setup(self): 

94 # Add callsite parameters. 

95 call_site_proc = structlog.processors.CallsiteParameterAdder( 

96 parameters=[ 

97 structlog.processors.CallsiteParameter.PATHNAME, 

98 structlog.processors.CallsiteParameter.MODULE, 

99 structlog.processors.CallsiteParameter.FUNC_NAME, 

100 structlog.processors.CallsiteParameter.LINENO, 

101 ] 

102 ) 

103 return [call_site_proc, self.__call__] 

104 

105 def __call__(self, logger, method_name, event_dict): 

106 location = { 

107 "file": event_dict.pop("pathname"), 

108 "line": str(event_dict.pop("lineno")), 

109 "function": f"{event_dict.pop('module')}:{event_dict.pop('func_name')}", 

110 } 

111 

112 event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY] = location 

113 

114 return event_dict