Skip to content
Closed
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
43 changes: 34 additions & 9 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,14 +598,22 @@ def _prepare_event(
before_send = self.options["before_send"]
if before_send is not None and event is not None:
new_event = None
exception_raised_in_before_send = False
with capture_internal_exceptions():
new_event = before_send(event, hint or {})
try:
new_event = before_send(event, hint or {})
except Exception:
exception_raised_in_before_send = True
raise
if new_event is None:
logger.info("before send dropped event")
if self.transport:
self.transport.record_lost_event(
"before_send", data_category="error"
reason = (
"callback_error"
if exception_raised_in_before_send
else "before_send"
)
self.transport.record_lost_event(reason, data_category="error")

# If this is an exception, reset the DedupeIntegration. It still
# remembers the dropped exception as the last exception, meaning
Expand Down Expand Up @@ -853,15 +861,32 @@ def _capture_telemetry(
exception_raised_in_before_send_func = True
raise

if ty in ("log", "metric"):
# We are ok with dropping metrics and logs when an exception is raised
# because we allow users to drop them in their respect before_send_*
# functions.
if ty == "log":
if exception_raised_in_before_send_func:
if self.transport:
self.transport.record_lost_event(
"callback_error", data_category="log_item"
)
return
if serialized is None:
if self.transport:
self.transport.record_lost_event(
"before_send", data_category="log_item"
)
return

elif ty == "metric":
if exception_raised_in_before_send_func:
if self.transport:
self.transport.record_lost_event(
"callback_error", data_category="trace_metric"
)
return
# Logs and metrics can be dropped in their respective
# before_send, so if we get None, don't queue them for sending.
if serialized is None:
if self.transport:
self.transport.record_lost_event(
"before_send", data_category="trace_metric"
)
return

elif ty == "span" and isinstance(telemetry, Span):
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/aws_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def event_processor(
if filtered_qs:
request["query_string"] = filtered_qs

if "headers" in aws_event:
if "headers" in aws_event and isinstance(aws_event["headers"], dict):
request["headers"] = _filter_headers(aws_event["headers"])

if data_collection["user_info"]:
Expand Down Expand Up @@ -428,7 +428,7 @@ def _get_url(aws_event: "Any", aws_context: "Any") -> str:
path = aws_event.get("path", None)

headers = aws_event.get("headers")
if headers is None:
if not isinstance(headers, dict):
headers = {}

host = headers.get("Host", None)
Expand Down
4 changes: 3 additions & 1 deletion sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -1480,7 +1480,9 @@ def run_error_processors(self, event: "Event", hint: "Hint") -> "Optional[Event]
)

for error_processor in error_processors:
new_event = error_processor(event, exc_info)
new_event = event
with capture_internal_exceptions():
new_event = error_processor(event, exc_info)
if new_event is None:
return self._drop(error_processor, "error processor")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies
# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry.

# Ignore everything
*

# But not index.py
!index.py

# And not .gitignore itself
!.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies
# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry.

# Ignore everything
*

# But not index.py
!index.py

# And not .gitignore itself
!.gitignore
33 changes: 33 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,39 @@ def foo(event, hint):
sentry_sdk.scope.global_event_processors = old_processors


@pytest.mark.tests_internal_exceptions
def test_before_send_exception_records_callback_error(
sentry_init, capture_events, capture_record_lost_event_calls
):
def bad_before_send(event, hint):
raise ValueError("before_send error")

sentry_init(before_send=bad_before_send, default_integrations=False)
events = capture_events()
record_lost_event_calls = capture_record_lost_event_calls()

capture_message("should be dropped")

assert len(events) == 0
assert ("callback_error", "error", None, 1) in record_lost_event_calls


def test_before_send_returning_none_records_before_send(
sentry_init, capture_events, capture_record_lost_event_calls
):
def dropping_before_send(event, hint):
return None

sentry_init(before_send=dropping_before_send)
events = capture_events()
record_lost_event_calls = capture_record_lost_event_calls()

capture_message("should be dropped")

assert len(events) == 0
assert ("before_send", "error", None, 1) in record_lost_event_calls


@pytest.mark.parametrize(
"installed_integrations, expected_name",
[
Expand Down
37 changes: 37 additions & 0 deletions tests/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,43 @@ def _before_log(record, hint):
assert not logs


@pytest.mark.tests_internal_exceptions
def test_logs_before_send_log_raises_records_callback_error(
sentry_init, capture_items, capture_record_lost_event_calls
):
def _before_log(record, hint):
raise ValueError("before_send_log error")

sentry_init(before_send_log=_before_log)
items = capture_items("log")
record_lost_event_calls = capture_record_lost_event_calls()

sentry_sdk.logger.error("This is an error log...")

get_client().flush()

assert not items
assert ("callback_error", "log_item", None, 1) in record_lost_event_calls


def test_logs_before_send_log_returns_none_records_before_send(
sentry_init, capture_items, capture_record_lost_event_calls
):
def _before_log(record, hint):
return None

sentry_init(before_send_log=_before_log)
items = capture_items("log")
record_lost_event_calls = capture_record_lost_event_calls()

sentry_sdk.logger.error("This is an error log...")

get_client().flush()

assert not items
assert ("before_send", "log_item", None, 1) in record_lost_event_calls


def test_logs_attributes(sentry_init, capture_items):
"""
Passing arbitrary attributes to log messages.
Expand Down
37 changes: 37 additions & 0 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,43 @@ def _before_metric(record, hint):
assert not metrics


@pytest.mark.tests_internal_exceptions
def test_metrics_before_send_raises_records_callback_error(
sentry_init, capture_items, capture_record_lost_event_calls
):
def _before_metric(record, hint):
raise ValueError("before_send_metric error")

sentry_init(before_send_metric=_before_metric)
items = capture_items("trace_metric")
record_lost_event_calls = capture_record_lost_event_calls()

sentry_sdk.metrics.count("test.keep", 1)

get_client().flush()

assert not items
assert ("callback_error", "trace_metric", None, 1) in record_lost_event_calls


def test_metrics_before_send_returns_none_records_before_send(
sentry_init, capture_items, capture_record_lost_event_calls
):
def _before_metric(record, hint):
return None

sentry_init(before_send_metric=_before_metric)
items = capture_items("trace_metric")
record_lost_event_calls = capture_record_lost_event_calls()

sentry_sdk.metrics.count("test.drop", 1)

get_client().flush()

assert not items
assert ("before_send", "trace_metric", None, 1) in record_lost_event_calls


def test_transport_format(sentry_init, capture_envelopes):
sentry_init(server_name="test-server", release="1.0.0")

Expand Down
23 changes: 23 additions & 0 deletions tests/tracing/test_span_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,29 @@ def traces_sampler(sampling_context):
assert span["attributes"]["drop"] is False


@pytest.mark.tests_internal_exceptions
def test_traces_sampler_exception_falls_back(sentry_init, capture_items):
def traces_sampler(sampling_context):
raise ValueError("traces_sampler error")

sentry_init(
traces_sampler=traces_sampler,
traces_sample_rate=1.0,
trace_lifecycle="stream",
)

items = capture_items("span")

with sentry_sdk.traces.start_span(name="test"):
...

sentry_sdk.get_client().flush()
spans = [item.payload for item in items]

# Falls back to traces_sample_rate=1.0, so span should still be sent
assert len(spans) == 1


def test_traces_sampler_called_once_per_segment(sentry_init):
traces_sampler_called = 0
span_name_in_traces_sampler = None
Expand Down
Loading