Coverage for structlog_gcp/errors.py: 100%

51 statements  

« prev     ^ index     » next       coverage.py v7.3.0, created at 2023-09-03 20:33 +0000

1import os 

2 

3import structlog 

4from structlog.typing import EventDict, Processor, WrappedLogger 

5 

6from .types import CLOUD_LOGGING_KEY, ERROR_EVENT_TYPE, SOURCE_LOCATION_KEY 

7 

8 

9class ServiceContext: 

10 def __init__(self, service: str | None = None, version: str | None = None) -> None: 

11 # https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically 

12 if service is None: 

13 service = os.environ.get("K_SERVICE", "unknown service") 

14 

15 if version is None: 

16 version = os.environ.get("K_REVISION", "unknown version") 

17 

18 self.service_context = {"service": service, "version": version} 

19 

20 def setup(self) -> list[Processor]: 

21 return [self] 

22 

23 def __call__( 

24 self, logger: WrappedLogger, method_name: str, event_dict: EventDict 

25 ) -> EventDict: 

26 """Add a service context in which an error has occurred. 

27 

28 This is part of the Error Reporting API, so it's only added when an error happens. 

29 """ 

30 

31 event_type = event_dict[CLOUD_LOGGING_KEY].get("@type") 

32 if event_type != ERROR_EVENT_TYPE: 

33 return event_dict 

34 

35 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext 

36 event_dict[CLOUD_LOGGING_KEY]["serviceContext"] = self.service_context 

37 

38 return event_dict 

39 

40 

41class ReportException: 

42 """Transform exception into a Google Cloud Error Reporting event.""" 

43 

44 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report 

45 # https://cloud.google.com/error-reporting/docs/formatting-error-messages#log-entry-examples 

46 

47 def __init__(self, log_level: str = "CRITICAL") -> None: 

48 self.log_level = log_level 

49 

50 def setup(self) -> list[Processor]: 

51 return [structlog.processors.format_exc_info, self] 

52 

53 def __call__( 

54 self, logger: WrappedLogger, method_name: str, event_dict: EventDict 

55 ) -> EventDict: 

56 exception = event_dict.pop("exception", None) 

57 if exception is None: 

58 return event_dict 

59 

60 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE 

61 event_dict[CLOUD_LOGGING_KEY]["severity"] = self.log_level 

62 

63 # https://cloud.google.com/error-reporting/docs/formatting-error-messages 

64 message = event_dict[CLOUD_LOGGING_KEY]["message"] 

65 error_message = f"{message}\n{exception}" 

66 event_dict[CLOUD_LOGGING_KEY]["stack_trace"] = error_message 

67 

68 return event_dict 

69 

70 

71class ReportError: 

72 """Report to Google Cloud Error Reporting specific log severities 

73 

74 This class assumes the :ref:`.processors.CodeLocation` processor ran before. 

75 """ 

76 

77 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report 

78 # https://cloud.google.com/error-reporting/docs/formatting-error-messages#log-entry-examples 

79 

80 def __init__(self, severities: list[str]) -> None: 

81 self.severities = severities 

82 

83 def setup(self) -> list[Processor]: 

84 return [self] 

85 

86 def _build_service_context(self) -> dict[str, str]: 

87 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext 

88 service_context = { 

89 # https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically 

90 "service": os.environ.get("K_SERVICE", "unknown service"), 

91 "version": os.environ.get("K_REVISION", "unknown version"), 

92 } 

93 

94 return service_context 

95 

96 def __call__( 

97 self, logger: WrappedLogger, method_name: str, event_dict: EventDict 

98 ) -> EventDict: 

99 severity = event_dict[CLOUD_LOGGING_KEY]["severity"] 

100 

101 if severity not in self.severities: 

102 return event_dict 

103 

104 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorContext 

105 error_context = { 

106 "reportLocation": event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY], 

107 } 

108 

109 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE 

110 event_dict[CLOUD_LOGGING_KEY]["context"] = error_context 

111 event_dict[CLOUD_LOGGING_KEY]["serviceContext"] = self._build_service_context() 

112 

113 return event_dict