From 6928937d1657303f8712f926c4754e6652c41368 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 3 Sep 2026 15:08:49 +0200 Subject: [PATCH 1/3] chore(sanic): Remove transaction-based tracing --- sentry_sdk/integrations/sanic.py | 69 ++++------ tests/integrations/sanic/test_sanic.py | 166 ++++++++----------------- 2 files changed, 77 insertions(+), 158 deletions(-) diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index 03f1594736..25b0821d4d 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -5,7 +5,6 @@ from urllib.parse import urlsplit import sentry_sdk -from sentry_sdk import continue_trace from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.data_collection import ( _apply_data_collection_filtering_to_query_string, @@ -15,7 +14,6 @@ from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import SegmentNameSource, StreamedSpan from sentry_sdk.tracing import TransactionSource -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, @@ -141,7 +139,6 @@ async def _context_enter(request: "Request") -> None: return client = sentry_sdk.get_client() - is_span_streaming_enabled = has_span_streaming_enabled(client.options) weak_request = weakref.ref(request) request.ctx._sentry_scope = sentry_sdk.isolation_scope() @@ -149,50 +146,34 @@ async def _context_enter(request: "Request") -> None: scope.clear_breadcrumbs() scope.add_event_processor(_make_request_processor(weak_request)) - if is_span_streaming_enabled: - integration = client.get_integration(SanicIntegration) - if ( - isinstance(integration, SanicIntegration) - and integration._unsampled_statuses - ): - logger.warning( - "The `unsampled_statuses` option of SanicIntegration has no effect when span streaming is enabled.", - ) + integration = client.get_integration(SanicIntegration) + if isinstance(integration, SanicIntegration) and integration._unsampled_statuses: + logger.warning( + "The `unsampled_statuses` option of SanicIntegration has no effect when span streaming is enabled.", + ) - sentry_sdk.traces.continue_trace(dict(request.headers)) - scope.set_custom_sampling_context({"sanic_request": request}) + sentry_sdk.traces.continue_trace(dict(request.headers)) + scope.set_custom_sampling_context({"sanic_request": request}) - if request.remote_addr: - if has_data_collection_enabled(client.options): - if client.options["data_collection"]["user_info"]: - scope.set_attribute(SPANDATA.USER_IP_ADDRESS, request.remote_addr) - elif should_send_default_pii(): + if request.remote_addr: + if has_data_collection_enabled(client.options): + if client.options["data_collection"]["user_info"]: scope.set_attribute(SPANDATA.USER_IP_ADDRESS, request.remote_addr) - - span = sentry_sdk.traces.start_span( - # Unless the request results in a 404 error, the name and source - # will get overwritten in _set_transaction - name=request.path, - attributes={ - "sentry.op": OP.HTTP_SERVER, - "sentry.origin": SanicIntegration.origin, - "sentry.segment.name.source": SegmentNameSource.URL.value, - }, - parent_span=None, - ) - request.ctx._sentry_root_span = span - else: - transaction = continue_trace( - dict(request.headers), - op=OP.HTTP_SERVER, - # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction - name=request.path, - source=TransactionSource.URL, - origin=SanicIntegration.origin, - ) - request.ctx._sentry_root_span = sentry_sdk.start_transaction( - transaction - ).__enter__() + elif should_send_default_pii(): + scope.set_attribute(SPANDATA.USER_IP_ADDRESS, request.remote_addr) + + span = sentry_sdk.traces.start_span( + # Unless the request results in a 404 error, the name and source + # will get overwritten in _set_transaction + name=request.path, + attributes={ + "sentry.op": OP.HTTP_SERVER, + "sentry.origin": SanicIntegration.origin, + "sentry.segment.name.source": SegmentNameSource.URL.value, + }, + parent_span=None, + ) + request.ctx._sentry_root_span = span async def _context_exit( diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index d85a0b3ad9..831b989571 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -360,7 +360,6 @@ def __init__( expected_status: int, expected_transaction_name: "Optional[str]", expected_source: "Optional[str]" = None, - streaming_compatible: bool = True, ) -> None: """ expected_transaction_name of None indicates we expect to not receive a transaction @@ -370,11 +369,9 @@ def __init__( self.expected_status = expected_status self.expected_transaction_name = expected_transaction_name self.expected_source = expected_source - self.streaming_compatible = streaming_compatible @pytest.mark.parametrize("send_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "test_config", [ @@ -402,14 +399,6 @@ def __init__( expected_transaction_name="fivehundred", expected_source=TransactionSource.COMPONENT, ), - TransactionTestConfig( - # By default, no transaction when we have a 404 error - integration_args=(), - url="/404", - expected_status=404, - expected_transaction_name=None, - streaming_compatible=False, - ), TransactionTestConfig( # With no ignored HTTP statuses, we should get transactions for 404 errors integration_args=(None,), @@ -418,40 +407,24 @@ def __init__( expected_transaction_name="/404", expected_source=TransactionSource.URL, ), - TransactionTestConfig( - # Transaction can be suppressed for other HTTP statuses, too, by passing config to the integration - integration_args=({200},), - url="/message", - expected_status=200, - expected_transaction_name=None, - streaming_compatible=False, - ), ], ) def test_transactions( test_config: "TransactionTestConfig", sentry_init: "Any", app: "Any", - capture_events: "Any", capture_items: "Any", - span_streaming: bool, send_pii: bool, ) -> None: - if span_streaming and not test_config.streaming_compatible: - pytest.skip("unsampled_statuses is not supported in span streaming mode") - # Init the SanicIntegration with the desired arguments sentry_init( integrations=[SanicIntegration(*test_config.integration_args)], traces_sample_rate=1.0, send_default_pii=send_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") # Make request to the desired URL c = get_client(app) @@ -461,89 +434,58 @@ def test_transactions( sentry_sdk.flush() - if span_streaming: - segments = [ - i.payload - for i in items - if i.payload["attributes"].get("sentry.origin") == "auto.http.sanic" - and i.payload["is_segment"] - ] - assert len(segments) <= 1 - (segment, *_) = [*segments, None] - - assert (segment is None) == (test_config.expected_transaction_name is None) - - if segment is not None: - assert segment["name"] == test_config.expected_transaction_name - assert ( - segment["attributes"]["sentry.segment.name.source"] - == test_config.expected_source - ) - - attrs = segment["attributes"] - assert attrs["http.request.method"] == "GET" - assert attrs["network.protocol.name"] == "http" - header_keys = { - key[len("http.request.header.") :] - for key in attrs - if key.startswith("http.request.header.") - } - assert header_keys >= {"accept", "accept-encoding", "host", "user-agent"} - assert attrs["http.response.status_code"] == test_config.expected_status - assert segment["status"] == ( - "error" if test_config.expected_status >= 400 else "ok" - ) - - if send_pii: - assert attrs["url.full"].endswith(test_config.url) - assert attrs["url.path"] == test_config.url.split("?")[0] - if "?" in test_config.url: - assert attrs["http.query"] == test_config.url.split("?", 1)[1] - - else: - assert "url.full" not in attrs - assert "url.path" not in attrs - assert "http.query" not in attrs + segments = [ + i.payload + for i in items + if i.payload["attributes"].get("sentry.origin") == "auto.http.sanic" + and i.payload["is_segment"] + ] + assert len(segments) <= 1 + (segment, *_) = [*segments, None] - else: - # Extract the transaction events by inspecting the event types. We should at most have 1 transaction event. - transaction_events = [ - e for e in events if "type" in e and e["type"] == "transaction" - ] - assert len(transaction_events) <= 1 - - # Get the only transaction event, or set to None if there are no transaction events. - (transaction_event, *_) = [*transaction_events, None] - - # We should have no transaction event if and only if we expect no transactions - assert (transaction_event is None) == ( - test_config.expected_transaction_name is None - ) + assert (segment is None) == (test_config.expected_transaction_name is None) - # If a transaction was expected, ensure it is correct - assert ( - transaction_event is None - or transaction_event["transaction"] == test_config.expected_transaction_name - ) + if segment is not None: + assert segment["name"] == test_config.expected_transaction_name assert ( - transaction_event is None - or transaction_event["transaction_info"]["source"] + segment["attributes"]["sentry.segment.name.source"] == test_config.expected_source ) + attrs = segment["attributes"] + assert attrs["http.request.method"] == "GET" + assert attrs["network.protocol.name"] == "http" + header_keys = { + key[len("http.request.header.") :] + for key in attrs + if key.startswith("http.request.header.") + } + assert header_keys >= {"accept", "accept-encoding", "host", "user-agent"} + assert attrs["http.response.status_code"] == test_config.expected_status + assert segment["status"] == ( + "error" if test_config.expected_status >= 400 else "ok" + ) + + if send_pii: + assert attrs["url.full"].endswith(test_config.url) + assert attrs["url.path"] == test_config.url.split("?")[0] + if "?" in test_config.url: + assert attrs["http.query"] == test_config.url.split("?", 1)[1] + + else: + assert "url.full" not in attrs + assert "url.path" not in attrs + assert "http.query" not in attrs + -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_span_origin(sentry_init, app, capture_events, capture_items, span_streaming): +def test_span_origin(sentry_init, app, capture_items): sentry_init( integrations=[SanicIntegration()], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") c = get_client(app) with c as client: @@ -551,16 +493,12 @@ def test_span_origin(sentry_init, app, capture_events, capture_items, span_strea sentry_sdk.flush() - if span_streaming: - (segment,) = [ - i.payload - for i in items - if i.payload["attributes"].get("sentry.origin") == "auto.http.sanic" - ] - assert segment["attributes"]["sentry.origin"] == "auto.http.sanic" - else: - (_, event) = events - assert event["contexts"]["trace"]["origin"] == "auto.http.sanic" + (segment,) = [ + i.payload + for i in items + if i.payload["attributes"].get("sentry.origin") == "auto.http.sanic" + ] + assert segment["attributes"]["sentry.origin"] == "auto.http.sanic" @pytest.mark.parametrize("init_kwargs, expect_ip", DATA_COLLECTION_USER_INFO_CASES) @@ -718,12 +656,10 @@ def test_url_query_data_collection_span_streaming( sentry_init, app, capture_items, init_kwargs, expected_query ): init_kwargs = dict(init_kwargs) - experiments = dict(init_kwargs.pop("_experiments", {})) - experiments["trace_lifecycle"] = "stream" sentry_init( integrations=[SanicIntegration()], traces_sample_rate=1.0, - _experiments=experiments, + trace_lifecycle="stream", **init_kwargs, ) @@ -743,7 +679,9 @@ def test_url_query_data_collection_span_streaming( and i.payload["is_segment"] ] - data_collection_enabled = "data_collection" in experiments + data_collection_enabled = "data_collection" in ( + init_kwargs.get("_experiments") or {} + ) url_attrs_expected = data_collection_enabled or init_kwargs.get( "send_default_pii", False ) From c6f64ca0f51c8b432aec345e163f042b151710d5 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 3 Sep 2026 16:01:42 +0200 Subject: [PATCH 2/3] fix name --- tests/integrations/sanic/test_sanic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index 831b989571..f4f6838828 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -652,7 +652,7 @@ def test_client_address_span_attribute_data_collection( @pytest.mark.parametrize( "init_kwargs, expected_query", _QUERY_PARAM_DATA_COLLECTION_CASES ) -def test_url_query_data_collection_span_streaming( +def test_url_query_data_collection( sentry_init, app, capture_items, init_kwargs, expected_query ): init_kwargs = dict(init_kwargs) From b386c020f977dab24527f1ee2043824ab94a2795 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 4 Sep 2026 11:24:36 +0200 Subject: [PATCH 3/3] . --- tests/integrations/sanic/test_sanic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index f4f6838828..edc895cc32 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -268,7 +268,7 @@ async def task(i): "headers": {}, "version": "1.1", "method": "GET", - "transport": None, + "transport": Mock(spec=["get_extra_info"]), } if SANIC_VERSION >= (19,):