Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/aws_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
24 changes: 15 additions & 9 deletions sentry_sdk/integrations/dedupe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -35,22 +40,23 @@ 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

# we can only weakref non builtin types
try:
integration._last_seen.set(weakref.ref(exc))
except TypeError:
integration._last_seen.set(exc)
integration._last_seen.set(_fingerprint(exc))

return event

Expand Down
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/serverless.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tests/integrations/launchdarkly/test_launchdarkly.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
52 changes: 46 additions & 6 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion tests/test_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"})
Expand Down
Loading