Coverage for structlog_gcp/error_reporting.py: 100%
43 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-21 14:36 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-21 14:36 +0000
1import os
3import structlog.processors
4from structlog.typing import EventDict, Processor, WrappedLogger
6from .types import CLOUD_LOGGING_KEY, ERROR_EVENT_TYPE, SOURCE_LOCATION_KEY
9def setup_exceptions(log_level: str = "CRITICAL") -> list[Processor]:
10 return [structlog.processors.format_exc_info, ReportException(log_level)]
13class ReportException:
14 """Transform exception into a Google Cloud Error Reporting event."""
16 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report
17 # https://cloud.google.com/error-reporting/docs/formatting-error-messages#log-entry-examples
19 def __init__(self, log_level: str = "CRITICAL") -> None:
20 self.log_level = log_level
22 def __call__(
23 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
24 ) -> EventDict:
25 exception = event_dict.pop("exception", None)
26 if exception is None:
27 return event_dict
29 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE
30 event_dict[CLOUD_LOGGING_KEY]["severity"] = self.log_level
32 # https://cloud.google.com/error-reporting/docs/formatting-error-messages
33 message = event_dict[CLOUD_LOGGING_KEY]["message"]
34 error_message = f"{message}\n{exception}"
35 event_dict[CLOUD_LOGGING_KEY]["stack_trace"] = error_message
37 return event_dict
40class ReportError:
41 """Report to Google Cloud Error Reporting specific log severities
43 This class assumes the :ref:`.processors.CodeLocation` processor ran before.
44 """
46 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report
47 # https://cloud.google.com/error-reporting/docs/formatting-error-messages#log-entry-examples
49 def __init__(self, severities: list[str]) -> None:
50 self.severities = severities
52 def __call__(
53 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
54 ) -> EventDict:
55 severity = event_dict[CLOUD_LOGGING_KEY]["severity"]
57 if severity not in self.severities:
58 return event_dict
60 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorContext
61 error_context = {
62 "reportLocation": event_dict[CLOUD_LOGGING_KEY][SOURCE_LOCATION_KEY],
63 }
65 event_dict[CLOUD_LOGGING_KEY]["@type"] = ERROR_EVENT_TYPE
66 event_dict[CLOUD_LOGGING_KEY]["context"] = error_context
68 # "serviceContext" should be added by the ServiceContext processor.
69 # event_dict[CLOUD_LOGGING_KEY]["serviceContext"]
71 return event_dict
74class ServiceContext:
75 def __init__(self, service: str | None = None, version: str | None = None) -> None:
76 # https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically
77 if service is None:
78 service = os.environ.get("K_SERVICE", "unknown service")
80 if version is None:
81 version = os.environ.get("K_REVISION", "unknown version")
83 self.service_context = {"service": service, "version": version}
85 def __call__(
86 self, logger: WrappedLogger, method_name: str, event_dict: EventDict
87 ) -> EventDict:
88 """Add a service context in which an error has occurred.
90 This is part of the Error Reporting API, so it's only added when an error happens.
91 """
93 event_type = event_dict[CLOUD_LOGGING_KEY].get("@type")
94 if event_type != ERROR_EVENT_TYPE:
95 return event_dict
97 # https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext
98 event_dict[CLOUD_LOGGING_KEY]["serviceContext"] = self.service_context
100 return event_dict