diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 37b649c67a..591be9e353 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -17,6 +17,7 @@ CLOUD_PLATFORM, CLOUD_PROVIDER, ) +from sentry_sdk.integrations.dedupe import DedupeIntegration from sentry_sdk.scope import Scope, should_send_default_pii from sentry_sdk.traces import SegmentNameSource from sentry_sdk.utils import ( @@ -134,6 +135,7 @@ def sentry_handler( timeout_thread = None with capture_internal_exceptions(): scope.clear_breadcrumbs() + DedupeIntegration.reset_last_seen() scope.add_event_processor( _make_request_event_processor( request_data, aws_context, configured_time diff --git a/sentry_sdk/integrations/dedupe.py b/sentry_sdk/integrations/dedupe.py index a0cc88081f..2c78204705 100644 --- a/sentry_sdk/integrations/dedupe.py +++ b/sentry_sdk/integrations/dedupe.py @@ -8,11 +8,16 @@ from sentry_sdk.utils import logger if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any, Optional, Tuple, Type from sentry_sdk._types import Event, Hint +def _fingerprint( + exc: BaseException, +) -> "Tuple[Type[BaseException], int, str]": + return (type(exc), hash(exc.args), hex(id(exc))) + class DedupeIntegration(Integration): identifier = "dedupe" @@ -35,14 +40,15 @@ def processor(event: "Event", hint: "Optional[Hint]") -> "Optional[Event]": return event last_seen = integration._last_seen.get(None) - if last_seen is not None: - # last_seen is either a weakref or the original instance - last_seen = ( - last_seen() if isinstance(last_seen, weakref.ref) else last_seen - ) - exc = exc_info[1] - if last_seen is exc: + + is_duplicate = False + if isinstance(last_seen, weakref.ref): + is_duplicate = last_seen() is exc + elif isinstance(last_seen, tuple): + is_duplicate = last_seen == _fingerprint(exc) + + if is_duplicate: logger.info("DedupeIntegration dropped duplicated error event %s", exc) return None @@ -50,7 +56,7 @@ def processor(event: "Event", hint: "Optional[Hint]") -> "Optional[Event]": try: integration._last_seen.set(weakref.ref(exc)) except TypeError: - integration._last_seen.set(exc) + integration._last_seen.set(_fingerprint(exc)) return event diff --git a/sentry_sdk/integrations/gcp.py b/sentry_sdk/integrations/gcp.py index bbddbdc53a..2f0e3870fe 100644 --- a/sentry_sdk/integrations/gcp.py +++ b/sentry_sdk/integrations/gcp.py @@ -11,6 +11,7 @@ from sentry_sdk.integrations import Integration from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.cloud_resource_context import CLOUD_PROVIDER +from sentry_sdk.integrations.dedupe import DedupeIntegration from sentry_sdk.scope import Scope, should_send_default_pii from sentry_sdk.traces import SegmentNameSource from sentry_sdk.utils import ( @@ -60,6 +61,7 @@ def sentry_func( with sentry_sdk.isolation_scope() as scope: with capture_internal_exceptions(): scope.clear_breadcrumbs() + DedupeIntegration.reset_last_seen() scope.add_event_processor( _make_request_event_processor( gcp_event, configured_time, initial_time diff --git a/sentry_sdk/integrations/serverless.py b/sentry_sdk/integrations/serverless.py index 16f91b28ae..7504a4921b 100644 --- a/sentry_sdk/integrations/serverless.py +++ b/sentry_sdk/integrations/serverless.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING import sentry_sdk +from sentry_sdk.integrations.dedupe import DedupeIntegration from sentry_sdk.utils import event_from_exception, reraise if TYPE_CHECKING: @@ -34,6 +35,7 @@ def wrapper(f: "F") -> "F": def inner(*args: "Any", **kwargs: "Any") -> "Any": with sentry_sdk.isolation_scope() as scope: scope.clear_breadcrumbs() + DedupeIntegration.reset_last_seen() try: return f(*args, **kwargs) diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index f456f7c99d..d3f10dd456 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -69,7 +69,7 @@ def task(flag_key): client.variation(flag_key, context, False) # use a tag to identify to identify events later on sentry_sdk.set_tag("task_id", flag_key) - sentry_sdk.capture_exception(Exception("something wrong!")) + sentry_sdk.capture_exception(Exception(f"{flag_key}: something wrong!")) # Capture an eval before we split isolation scopes. client.variation("hello", context, False) diff --git a/tests/test_basics.py b/tests/test_basics.py index eb4eb516fe..39e0ee5413 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -122,6 +122,46 @@ def test_generic_mechanism(sentry_init, capture_events): assert event["exception"]["values"][0]["mechanism"]["handled"] +def test_dedupe_builtin_exceptions(sentry_init, capture_events): + def do_this(): + try: + raise ValueError("hello world!") + except Exception: + capture_exception() + + sentry_init() + events = capture_events() + + do_this() + do_this() + + assert len(events) == 1, "Built-in exceptions are not deduplicated" + + +def test_dedupe_builtin_exceptions_different_raise_sites(sentry_init, capture_events): + def raise_here(): + try: + raise ValueError("hello world!") + except Exception: + capture_exception() + + def raise_there(): + try: + raise ValueError("hello world!") + except Exception: + capture_exception() + + sentry_init() + events = capture_events() + + raise_here() + raise_there() + + assert len(events) == 2, ( + "Built-in exceptions with the exact same type and arguments raised from different code paths are not deduplicated" + ) + + def test_option_before_send(sentry_init, capture_events): def before_send(event, hint): event["extra"] = {"before_send_called": True} @@ -187,19 +227,19 @@ def before_breadcrumb(crumb, hint): sentry_sdk.get_client().transport, "record_lost_event", record_lost_event ) - def do_this(): + def do_this(msg): add_breadcrumb(message="Hello", hint={"foo": 42}) try: - raise ValueError("aha!") + raise ValueError(msg) except Exception: capture_exception() - do_this() + do_this("aha!") drop_breadcrumbs = True - do_this() + do_this("another aha!") assert not reports drop_events = True - do_this() + do_this("why not one more aha!") assert reports == [("before_send", "error")] normal, no_crumbs = events @@ -284,7 +324,7 @@ def test_breadcrumbs(sentry_init, capture_events): sentry_sdk.get_isolation_scope().clear() - capture_exception(ValueError()) + capture_exception(ValueError("another one!")) (event,) = events assert len(event["breadcrumbs"]["values"]) == 0 diff --git a/tests/test_scope.py b/tests/test_scope.py index cba5b50298..f65a20edf1 100644 --- a/tests/test_scope.py +++ b/tests/test_scope.py @@ -11,6 +11,7 @@ new_scope, ) from sentry_sdk.client import Client, NonRecordingClient +from sentry_sdk.integrations.dedupe import DedupeIntegration from sentry_sdk.scope import ( Scope, ScopeType, @@ -69,7 +70,7 @@ def test_scope_flags_copy(): def test_set_user(sentry_init, capture_events): - sentry_init() + sentry_init(disabled_integrations=[DedupeIntegration]) events = capture_events() sentry_sdk.get_isolation_scope().set_user({"id": "42", "email": "bob@example.com"})