Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 0 additions & 8 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
)
from sentry_sdk.envelope import Envelope, Item
from sentry_sdk.integrations import setup_integrations
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.monitor import Monitor
from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler
from sentry_sdk.scrubber import EventScrubber
Expand Down Expand Up @@ -607,13 +606,6 @@ def _prepare_event(
"before_send", data_category="error"
)

# If this is an exception, reset the DedupeIntegration. It still
# remembers the dropped exception as the last exception, meaning
# that if the same exception happens again and is not dropped
# in before_send, it'd get dropped by DedupeIntegration.
if event.get("exception"):
DedupeIntegration.reset_last_seen()

event = new_event

return event
Expand Down
39 changes: 8 additions & 31 deletions sentry_sdk/integrations/dedupe.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import weakref
from contextvars import ContextVar
from typing import TYPE_CHECKING

import sentry_sdk
from sentry_sdk.integrations import Integration
from sentry_sdk.scope import add_global_event_processor
from sentry_sdk.utils import logger
from sentry_sdk.utils import capture_internal_exceptions, logger

if TYPE_CHECKING:
from typing import Any, Optional
from typing import Optional

from sentry_sdk._types import Event, Hint


class DedupeIntegration(Integration):
identifier = "dedupe"

def __init__(self) -> None:
self._last_seen: "ContextVar[Any]" = ContextVar("last-seen")

@staticmethod
def setup_once() -> None:
@add_global_event_processor
Expand All @@ -34,30 +29,12 @@ def processor(event: "Event", hint: "Optional[Hint]") -> "Optional[Event]":
if exc_info is None:
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:

if getattr(exc, "_handled_by_sentry", False):
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)

return event

@staticmethod
def reset_last_seen() -> None:
integration = sentry_sdk.get_client().get_integration(DedupeIntegration)
if integration is None:
return

integration._last_seen.set(None)
else:
with capture_internal_exceptions():
exc._handled_by_sentry = True
return event
Comment thread
ericapisani marked this conversation as resolved.
25 changes: 23 additions & 2 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,13 +614,34 @@ def before_send(event, hint):
sentry_init(before_send=before_send)
events = capture_events()

exc = ValueError("aha!")
for _ in range(2):
# The first ValueError will be dropped by before_send. The second
# ValueError will be accepted by before_send, and should be sent to
# Sentry.
try:
raise exc
raise ValueError("aha!")
except Exception:
capture_exception()

assert len(events) == 1


def test_dedupe_drops_exception_when_seen_a_second_time(sentry_init, capture_events):
"""
This test is intended to emulate behavior seen in frameworks like Django,
where an exception is raised in a view and then is re-raised in middleware.

In cases like that we don't want to send a second event for that exception.
"""
sentry_init()
events = capture_events()

test = None
for _ in range(2):
try:
if test is None:
test = ValueError("foo")
raise test
except Exception:
capture_exception()

Expand Down
Loading