From 9dce00b19167e0dcce73c2ee0dfea59460401810 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 28 Sep 2026 10:52:54 +0200 Subject: [PATCH 1/5] fix: Unsample on sampler exception --- sentry_sdk/profiler/transaction_profiler.py | 7 +++---- sentry_sdk/tracing.py | 9 +++------ sentry_sdk/tracing_utils.py | 7 ++----- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/sentry_sdk/profiler/transaction_profiler.py b/sentry_sdk/profiler/transaction_profiler.py index 3ed1da8429..8b6c55a2eb 100644 --- a/sentry_sdk/profiler/transaction_profiler.py +++ b/sentry_sdk/profiler/transaction_profiler.py @@ -290,12 +290,11 @@ def _set_initial_sampling_decision( sample_rate = options["profiles_sampler"](sampling_context) except Exception: logger.warning( - "[Profiling] profiles_sampler raised; falling back to profiles_sample_rate", + "[Profiling] profiles_sampler raised; unsampling profile", exc_info=True, ) - sample_rate = options["profiles_sample_rate"] - if sample_rate is None: - sample_rate = options["_experiments"].get("profiles_sample_rate") + self.sampled = False + return elif options["profiles_sample_rate"] is not None: sample_rate = options["profiles_sample_rate"] else: diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index aab2794621..38922773ea 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1217,14 +1217,11 @@ def _set_initial_sampling_decision( sample_rate = client.options["traces_sampler"](sampling_context) except Exception: logger.warning( - "[Tracing] traces_sampler raised; falling back to parent sample rate or traces_sample_rate", + "[Tracing] traces_sampler raised; unsampling trace", exc_info=True, ) - sample_rate = ( - sampling_context["parent_sampled"] - if sampling_context["parent_sampled"] is not None - else client.options["traces_sample_rate"] - ) + self.sampled = False + return else: sample_rate = ( sampling_context["parent_sampled"] diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 42ebc7f1b3..3bb300e267 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -1702,13 +1702,10 @@ def _make_sampling_decision( sample_rate = client.options["traces_sampler"](sampling_context) except Exception: logger.warning( - "[Tracing] traces_sampler raised; falling back to parent sample rate or traces_sample_rate", + "[Tracing] traces_sampler raised; unsampling trace", exc_info=True, ) - if propagation_context.parent_sampled is not None: - sample_rate = propagation_context.parent_sampled - else: - sample_rate = client.options["traces_sample_rate"] + return False, None, None, "sample_rate" else: if propagation_context.parent_sampled is not None: sample_rate = propagation_context.parent_sampled From a2e79cfaf2a6585d2cd4efa1500de306f02556d0 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 28 Sep 2026 11:07:04 +0200 Subject: [PATCH 2/5] tests --- tests/profiler/test_transaction_profiler.py | 5 +- tests/tracing/test_sampling.py | 104 +------------------- tests/tracing/test_span_streaming.py | 8 +- 3 files changed, 8 insertions(+), 109 deletions(-) diff --git a/tests/profiler/test_transaction_profiler.py b/tests/profiler/test_transaction_profiler.py index 0d185a47af..6f956790cb 100644 --- a/tests/profiler/test_transaction_profiler.py +++ b/tests/profiler/test_transaction_profiler.py @@ -238,7 +238,7 @@ def test_profiles_sampler( @pytest.mark.tests_internal_exceptions @mock.patch("sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0) -def test_profiles_sampler_exception_falls_back_to_profiles_sample_rate( +def test_profiles_sampler_exception_unsamples( sentry_init, capture_envelopes, teardown_profiling, @@ -263,8 +263,7 @@ def bad_sampler(sampling_context): items[item.type].append(item) assert len(items["transaction"]) == 1 - # Falls back to profiles_sample_rate=1.0, so profile should be sent - assert len(items["profile"]) == 1 + assert len(items["profile"]) == 0 def test_minimum_unique_samples_required( diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index 0685498d13..1c95537d42 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -198,117 +198,17 @@ def test_tolerates_traces_sampler_returning_a_boolean_span_streaming( assert span.sampled is traces_sampler_return_value -@pytest.mark.parametrize( - "traces_sample_rate,expected_decision", - [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], -) -def test_traces_sampler_raising_falls_back_to_traces_sample_rate( - sentry_init, - traces_sample_rate, - expected_decision, -): +def test_traces_sampler_raising_unsamples(sentry_init, traces_sample_rate): sentry_init( traces_sampler=mock.Mock(side_effect=ValueError("boom")), - traces_sample_rate=traces_sample_rate, + traces_sample_rate=1.0, ) baggage = Baggage(sentry_items={"sample_rand": "0.500000"}) transaction = start_transaction(name="dogpark", baggage=baggage) - assert transaction.sampled is expected_decision - - -@pytest.mark.parametrize("parent_sampling_decision", [True, False]) -def test_traces_sampler_raising_falls_back_to_parent_sampling_decision( - sentry_init, parent_sampling_decision -): - # set traces_sample_rate to produce the opposite of the parent decision, - # to prove the parent's decision takes precedence in the fallback - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - traces_sample_rate=0.0 if parent_sampling_decision else 1.0, - ) - - baggage = Baggage(sentry_items={"sample_rand": "0.500000"}) - transaction = start_transaction( - name="dogpark", baggage=baggage, parent_sampled=parent_sampling_decision - ) - assert transaction.sampled is parent_sampling_decision - - -@pytest.mark.parametrize( - "traces_sample_rate,expected_decision", - [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], -) -def test_traces_sampler_raising_falls_back_to_traces_sample_rate_span_streaming( - sentry_init, - traces_sample_rate, - expected_decision, -): - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - traces_sample_rate=traces_sample_rate, - trace_lifecycle="stream", - ) - - sentry_sdk.traces.continue_trace( - { - "sentry-trace": "0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331", - "baggage": "sentry-sample_rand=0.500000", - } - ) - - with sentry_sdk.traces.start_span(name="dogpark") as span: - assert span.sampled is expected_decision - - -@pytest.mark.parametrize( - "traces_sample_rate,expected_decision", - [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], -) -def test_traces_sampler_raising_no_incoming_trace_falls_back_to_traces_sample_rate_span_streaming( - sentry_init, - traces_sample_rate, - expected_decision, - monkeypatch, -): - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - traces_sample_rate=traces_sample_rate, - trace_lifecycle="stream", - ) - - # no continue_trace, so no propagated sample_rand; make it deterministic - monkeypatch.setattr( - "sentry_sdk.tracing_utils._generate_sample_rand", lambda *a, **kw: 0.5 - ) - - with sentry_sdk.traces.start_span(name="dogpark") as span: - assert span.sampled is expected_decision - - -def test_traces_sampler_raising_no_incoming_trace_and_no_traces_sample_rate( - sentry_init, -): - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - ) - - transaction = start_transaction(name="dogpark") assert transaction.sampled is False -def test_traces_sampler_raising_no_incoming_trace_and_no_traces_sample_rate_span_streaming( - sentry_init, -): - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - trace_lifecycle="stream", - ) - - with sentry_sdk.traces.start_span(name="dogpark") as span: - assert span.sampled is False - - @pytest.mark.parametrize("sampling_decision", [True, False]) def test_only_captures_transaction_when_sampled_is_true( sentry_init, sampling_decision, capture_events diff --git a/tests/tracing/test_span_streaming.py b/tests/tracing/test_span_streaming.py index 5de65e4802..c1d9730ef6 100644 --- a/tests/tracing/test_span_streaming.py +++ b/tests/tracing/test_span_streaming.py @@ -565,7 +565,7 @@ def traces_sampler(sampling_context): @pytest.mark.tests_internal_exceptions -def test_traces_sampler_exception_falls_back(sentry_init, capture_items): +def test_traces_sampler_exception_unsamples(sentry_init, capture_items): def traces_sampler(sampling_context): raise ValueError("traces_sampler error") @@ -580,11 +580,11 @@ def traces_sampler(sampling_context): with sentry_sdk.traces.start_span(name="test"): ... - sentry_sdk.get_client().flush() + sentry_sdk.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 + assert not spans def test_traces_sampler_called_once_per_segment(sentry_init): From 5423426231ebed6b8caefb641da14e8bf338c2f3 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 28 Sep 2026 11:08:43 +0200 Subject: [PATCH 3/5] fix reason --- sentry_sdk/tracing_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 3bb300e267..8a6961ba15 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -1705,7 +1705,7 @@ def _make_sampling_decision( "[Tracing] traces_sampler raised; unsampling trace", exc_info=True, ) - return False, None, None, "sample_rate" + return False, None, None, "callback_error" else: if propagation_context.parent_sampled is not None: sample_rate = propagation_context.parent_sampled From 32f31e2868e2ee174121182a34ce6d195e2aaf59 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 28 Sep 2026 11:15:31 +0200 Subject: [PATCH 4/5] fix --- tests/tracing/test_sampling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index 1c95537d42..ddfaa90f82 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -198,7 +198,7 @@ def test_tolerates_traces_sampler_returning_a_boolean_span_streaming( assert span.sampled is traces_sampler_return_value -def test_traces_sampler_raising_unsamples(sentry_init, traces_sample_rate): +def test_traces_sampler_raising_unsamples(sentry_init): sentry_init( traces_sampler=mock.Mock(side_effect=ValueError("boom")), traces_sample_rate=1.0, From fc78d20320ecfd670c98633b6158dc789fd000a5 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 28 Sep 2026 11:24:21 +0200 Subject: [PATCH 5/5] ci