Skip to content
Merged
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
9 changes: 9 additions & 0 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,18 @@ def _prepare_event(
event["timestamp"] = datetime.utcnow()

if scope is not None:
is_transaction = event.get("type") == "transaction"
event_ = scope.apply_to_event(event, hint)

# one of the event/error processors returned None
if event_ is None:
if self.transport:
self.transport.record_lost_event(
"event_processor",
data_category=("transaction" if is_transaction else "error"),
)
return None

event = event_

if (
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,25 @@ def append_envelope(envelope):
return inner


@pytest.fixture
def capture_client_reports(monkeypatch):
def inner():
reports = []
test_client = sentry_sdk.Hub.current.client

def record_lost_event(reason, data_category=None, item=None):
if data_category is None:
data_category = item.data_category
return reports.append((reason, data_category))

monkeypatch.setattr(
test_client.transport, "record_lost_event", record_lost_event
)
return reports

return inner


@pytest.fixture
def capture_events_forksafe(monkeypatch, capture_events, request):
def inner():
Expand Down
3 changes: 3 additions & 0 deletions tests/integrations/gcp/test_gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def init_sdk(timeout_warning=False, **extra_init_args):
transport=TestTransport,
integrations=[GcpIntegration(timeout_warning=timeout_warning)],
shutdown_timeout=10,
# excepthook -> dedupe -> event_processor client report gets added
# which we don't really care about for these tests
send_client_reports=False,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just turned client reports off for the GCP tests since the actual problem is unrelated and caused by the same exception being caught twice and dropped by the DedupeIntegration event processor.

I added a separate test for the case below.

**extra_init_args
)

Expand Down
60 changes: 60 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys
import logging

import pytest
Expand All @@ -10,13 +11,19 @@
capture_event,
capture_exception,
capture_message,
start_transaction,
add_breadcrumb,
last_event_id,
Hub,
)

from sentry_sdk._compat import reraise
from sentry_sdk.integrations import _AUTO_ENABLING_INTEGRATIONS
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.scope import ( # noqa: F401
add_global_event_processor,
global_event_processors,
)


def test_processors(sentry_init, capture_events):
Expand Down Expand Up @@ -371,3 +378,56 @@ def test_capture_event_with_scope_kwargs(sentry_init, capture_events):
(event,) = events
assert event["level"] == "info"
assert event["extra"]["foo"] == "bar"


def test_dedupe_event_processor_drop_records_client_report(
sentry_init, capture_events, capture_client_reports
):
"""
DedupeIntegration internally has an event_processor that filters duplicate exceptions.
We want a duplicate exception to be captured only once and the drop being recorded as
a client report.
"""
sentry_init()
events = capture_events()
reports = capture_client_reports()

try:
raise ValueError("aha!")
except Exception:
try:
capture_exception()
reraise(*sys.exc_info())
except Exception:
capture_exception()

(event,) = events
(report,) = reports

assert event["level"] == "error"
assert "exception" in event
assert report == ("event_processor", "error")


def test_event_processor_drop_records_client_report(
sentry_init, capture_events, capture_client_reports
):
sentry_init(traces_sample_rate=1.0)
events = capture_events()
reports = capture_client_reports()

global global_event_processors

@add_global_event_processor
def foo(event, hint):
return None

capture_message("dropped")

with start_transaction(name="dropped"):
pass

assert len(events) == 0
assert reports == [("event_processor", "error"), ("event_processor", "transaction")]

global_event_processors.pop()