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
« prev ^ index » next coverage.py v7.3.0, created at 2023-09-03 20:33 +0000
1import os
3import structlog
4from structlog.typing import EventDict, Processor, WrappedLogger
6from .types import CLOUD_LOGGING_KEY, ERROR_EVENT_TYPE, SOURCE_LOCATION_KEY
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")
15 if version is None:
16 version = os.environ.get("K_REVISION", "unknown version")
18 self.service_context = {"service": service, "version": version}
20 def setup(self) -> list[Processor]:
21 return [self]
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.
28 This is part of the Error Reporting API, so it's only added when an error happens.
29 """
31 event_type = event_dict[CLOUD_LOGGING_KEY].get("@type")
32 if event_type != ERROR_EVENT_TYPE:
33 return event_dict
35 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext
36 event_dict[CLOUD_LOGGING_KEY]["serviceContext"] = self.service_context
38 return event_dict
41class ReportException:
42 """Transform exception into a Google Cloud Error Reporting event."""
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
47 def __init__(self, log_level: str = "CRITICAL") -> None:
48 self.log_level = log_level
50 def setup(self) -> list[Processor]:
51 return [structlog.processors.format_exc_info, self]
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
60 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE
61 event_dict[CLOUD_LOGGING_KEY]["severity"] = self.log_level
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
68 return event_dict
71class ReportError:
72 """Report to Google Cloud Error Reporting specific log severities
74 This class assumes the :ref:`.processors.CodeLocation` processor ran before.
75 """
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
80 def __init__(self, severities: list[str]) -> None:
81 self.severities = severities
83 def setup(self) -> list[Processor]:
84 return [self]
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 }
94 return service_context
96 def __call__(
97 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
98 ) -> EventDict:
99 severity = event_dict[CLOUD_LOGGING_KEY]["severity"]
101 if severity not in self.severities:
102 return event_dict
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 }
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()
113 return event_dict