Coverage for structlog_gcp/errors.py: 100%
43 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
1import os
3import structlog
4from structlog.typing import EventDict, Processor, WrappedLogger
6from .types import CLOUD_LOGGING_KEY, ERROR_EVENT_TYPE, SOURCE_LOCATION_KEY
9def add_service_context(
10 logger: WrappedLogger, method_name: str, event_dict: EventDict
11) -> EventDict:
12 """Add a service context in which an error has occurred.
14 This is part of the Error Reporting API, so it's only added when an error happens.
15 """
17 event_type = event_dict[CLOUD_LOGGING_KEY].get("@type")
18 if event_type != ERROR_EVENT_TYPE:
19 return event_dict
21 service_context = {
22 # https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically
23 "service": os.environ.get("K_SERVICE", "unknown service"),
24 "version": os.environ.get("K_REVISION", "unknown version"),
25 }
27 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext
28 event_dict[CLOUD_LOGGING_KEY]["serviceContext"] = service_context
30 return event_dict
33class ReportException:
34 """Transform exception into a Google Cloud Error Reporting event."""
36 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report
37 # https://cloud.google.com/error-reporting/docs/formatting-error-messages#log-entry-examples
39 def __init__(self, log_level: str = "CRITICAL") -> None:
40 self.log_level = log_level
42 def setup(self) -> list[Processor]:
43 return [structlog.processors.format_exc_info, self]
45 def __call__(
46 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
47 ) -> EventDict:
48 exception = event_dict.pop("exception", None)
49 if exception is None:
50 return event_dict
52 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE
53 event_dict[CLOUD_LOGGING_KEY]["severity"] = self.log_level
55 # https://cloud.google.com/error-reporting/docs/formatting-error-messages
56 message = event_dict[CLOUD_LOGGING_KEY]["message"]
57 error_message = f"{message}\n{exception}"
58 event_dict[CLOUD_LOGGING_KEY]["stack_trace"] = error_message
60 return event_dict
63class ReportError:
64 """Report to Google Cloud Error Reporting specific log severities
66 This class assumes the :ref:`.processors.CodeLocation` processor ran before.
67 """
69 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report
70 # https://cloud.google.com/error-reporting/docs/formatting-error-messages#log-entry-examples
72 def __init__(self, severities: list[str]) -> None:
73 self.severities = severities
75 def setup(self) -> list[Processor]:
76 return [self]
78 def _build_service_context(self) -> dict[str, str]:
79 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext
80 service_context = {
81 # https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically
82 "service": os.environ.get("K_SERVICE", "unknown service"),
83 "version": os.environ.get("K_REVISION", "unknown version"),
84 }
86 return service_context
88 def __call__(
89 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
90 ) -> EventDict:
91 severity = event_dict[CLOUD_LOGGING_KEY]["severity"]
93 if severity not in self.severities:
94 return event_dict
96 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorContext
97 error_context = {
98 "reportLocation": event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY],
99 }
101 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE
102 event_dict[CLOUD_LOGGING_KEY]["context"] = error_context
103 event_dict[CLOUD_LOGGING_KEY]["serviceContext"] = self._build_service_context()
105 return event_dict