diff --git a/README.md b/README.md index c1fe86e..1e70859 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,11 @@ Do not forget to add the `structlog.stdlib.add_log_level` and optionally the as events. Default is `logging.WARNING`. - `active` A flag to make this processor enabled/disabled. - `as_context` Send `event_dict` as extra info to Sentry. Default is `True`. +- `ignore_breadcrumb_data` A list of data keys that will be excluded from + [breadcrumb data](https://docs.sentry.io/platforms/python/enriching-events/breadcrumbs/#manual-breadcrumbs). + Defaults to keys which are already sent separately, i.e. `level`, `logger`, + `event` and `timestamp`. All other data in `event_dict` will be sent as + breadcrumb data. - `tag_keys` A list of keys. If any if these keys appear in `event_dict`, the key and its corresponding value in `event_dict` will be used as Sentry event tags. use `"__all__"` to report all key/value pairs of event as tags. diff --git a/structlog_sentry/__init__.py b/structlog_sentry/__init__.py index b532fe9..925ea81 100644 --- a/structlog_sentry/__init__.py +++ b/structlog_sentry/__init__.py @@ -38,6 +38,12 @@ def __init__( event_level: int = logging.WARNING, active: bool = True, as_context: bool = True, + ignore_breadcrumb_data: Iterable[str] = ( + "level", + "logger", + "event", + "timestamp", + ), tag_keys: list[str] | str | None = None, ignore_loggers: Iterable[str] | None = None, verbose: bool = False, @@ -51,6 +57,8 @@ def __init__( :param active: A flag to make this processor enabled/disabled. :param as_context: Send `event_dict` as extra info to Sentry. Default is :obj:`True`. + :param ignore_breadcrumb_data: A list of data keys that will be excluded from + breadcrumb data. Defaults to keys which are already sent separately. :param tag_keys: A list of keys. If any if these keys appear in `event_dict`, the key and its corresponding value in `event_dict` will be used as Sentry event tags. use `"__all__"` to report all key/value pairs of event as tags. @@ -68,6 +76,7 @@ def __init__( self._hub = hub self._as_context = as_context self._original_event_dict: dict = {} + self.ignore_breadcrumb_data = ignore_breadcrumb_data self._ignored_loggers: set[str] = set() if ignore_loggers is not None: @@ -131,13 +140,16 @@ def _get_event_and_hint(self, event_dict: EventDict) -> tuple[dict, dict]: return event, hint def _get_breadcrumb_and_hint(self, event_dict: EventDict) -> tuple[dict, dict]: + data = { + k: v for k, v in event_dict.items() if k not in self.ignore_breadcrumb_data + } event = { "type": "log", "level": event_dict.get("level"), # type: ignore "category": event_dict.get("logger"), "message": event_dict["event"], "timestamp": event_dict.get("timestamp"), - "data": {}, + "data": data, } return event, {"log_record": event_dict} diff --git a/test/test_sentry_processor.py b/test/test_sentry_processor.py index fd9ba27..ae2d5c8 100644 --- a/test/test_sentry_processor.py +++ b/test/test_sentry_processor.py @@ -10,7 +10,6 @@ LoggingIntegration(event_level=None, level=None), ] - # Register custom log level CUSTOM_LOG_LEVEL_NAME = "CUSTOM_LEVEL" CUSTOM_LOG_LEVEL_VALUE = logging.DEBUG @@ -302,3 +301,72 @@ def test_sentry_json_respects_global_with_locals_option_with_locals(sentry_event for event in sentry_events: for frame in event["exception"]["values"][0]["stacktrace"]["frames"]: assert "vars" in frame # Local variables were captured + + +base_info_log = { + "level": "info", + "event": "Info message", + "logger": "EventLogger", + "timestamp": "2024-01-01T00:00:00Z", +} +base_error_log = { + "level": "error", + "event": "Error message", +} + + +def test_breadcrumbs_with_additional_data(sentry_events): + processor = SentryProcessor(verbose=True) + processor(None, None, {**base_info_log, **{"foo": "bar"}}) + processor(None, None, base_error_log) + print({**base_info_log, **{"foo": "bar"}}) + breadcrumbs = sentry_events[0]["breadcrumbs"]["values"] + del breadcrumbs[0]["timestamp"] + assert breadcrumbs[0] == { + "type": "log", + "level": "info", + "category": "EventLogger", + "message": "Info message", + "data": {"foo": "bar"}, + } + + +def test_breadcrumbs_with_custom_exclusions(sentry_events): + processor = SentryProcessor(verbose=True, ignore_breadcrumb_data=["foo"]) + processor(None, None, {**base_info_log, **{"foo": "bar"}}) + processor(None, None, base_error_log) + + breadcrumbs = sentry_events[0]["breadcrumbs"]["values"] + + del breadcrumbs[0]["timestamp"] + assert breadcrumbs[0] == { + "type": "log", + "level": "info", + "category": "EventLogger", + "message": "Info message", + "data": { + "level": "info", + "event": "Info message", + "logger": "EventLogger", + "timestamp": "2024-01-01T00:00:00Z", + }, + } + + +def test_breadcrumbs_with_no_additional_data(sentry_events): + processor = SentryProcessor(verbose=True) + processor(None, None, base_info_log) + processor(None, None, base_error_log) + + breadcrumbs = sentry_events[0]["breadcrumbs"]["values"] + + assert len(breadcrumbs) == 1 + assert isinstance(breadcrumbs[0]["timestamp"], str) + del breadcrumbs[0]["timestamp"] + assert breadcrumbs[0] == { + "type": "log", + "level": "info", + "category": "EventLogger", + "message": "Info message", + "data": {}, + }