From c7134d3327ec5ff23cd4b0314317f28beaf89ef9 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 2 Sep 2026 14:12:57 +0200 Subject: [PATCH 1/2] chore(fastapi): Drop transactions --- sentry_sdk/integrations/fastapi.py | 16 +- tests/integrations/fastapi/test_fastapi.py | 637 +++++++-------------- 2 files changed, 197 insertions(+), 456 deletions(-) diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index 4aa5b4d791..145ff58613 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -8,7 +8,6 @@ from sentry_sdk.integrations import DidNotEnable, _check_minimum_version from sentry_sdk.traces import StreamedSpan, get_current_span from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( has_data_collection_enabled, parse_version, @@ -192,18 +191,11 @@ def _sentry_get_request_handler(*args: "Any", **kwargs: "Any") -> "Any": @wraps(old_call) def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any": - current_scope = sentry_sdk.get_current_scope() + current_span = sentry_sdk.traces.get_current_span() - client = sentry_sdk.get_client() - if has_span_streaming_enabled(client.options): - current_span = current_scope.streamed_span - - if type(current_span) is StreamedSpan: - segment = current_span._segment - segment._update_active_thread() - - elif current_scope.transaction is not None: - current_scope.transaction.update_active_thread() + if type(current_span) is StreamedSpan: + segment = current_span._segment + segment._update_active_thread() return old_call(*args, **kwargs) diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 2b327eb6f0..3ae26fa50e 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -125,238 +125,146 @@ async def body_form( @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_request_info_json_body( - sentry_init, capture_events, capture_items, span_streaming -): +async def test_request_info_json_body(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) app = fastapi_app_factory() client = TestClient(app) - if span_streaming: - items = capture_items("event", "span") - - client.post( - "/body/json", - json=BODY_JSON, - headers={ - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) - - (event,) = (item.payload for item in items if item.type == "event") - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - assert event["request"]["data"] == BODY_JSON - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) + items = capture_items("event", "span") - assert json.loads( - server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] - ) == {"some": "json", "for": "testing", "nested": {"numbers": 123}} - else: - events = capture_events() - - client.post( - "/body/json", - json=BODY_JSON, - headers={ - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) + client.post( + "/body/json", + json=BODY_JSON, + headers={ + "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", + }, + ) - (event, transaction_event) = events + (event,) = (item.payload for item in items if item.type == "event") + assert event["request"]["cookies"] == { + "tasty_cookie": "strawberry", + "yummy_cookie": "choco", + } + assert event["request"]["data"] == BODY_JSON - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - assert event["request"]["data"] == BODY_JSON + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - assert transaction_event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - assert transaction_event["request"]["data"] == BODY_JSON + assert json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) == { + "some": "json", + "for": "testing", + "nested": {"numbers": 123}, + } @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_formdata_request_body( - sentry_init, capture_events, capture_items, span_streaming -): +async def test_formdata_request_body(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, max_request_body_size="always", integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) app = fastapi_app_factory() client = TestClient(app) - if span_streaming: - items = capture_items("event", "span") - - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - }, - ) - - (event,) = (item.payload for item in items if item.type == "event") - assert event["request"]["data"].keys() == PARSED_FORM.keys() - assert event["request"]["data"]["username"] == PARSED_FORM["username"] - assert event["request"]["data"]["password"] == "[Filtered]" - assert event["request"]["data"]["photo"] == "" + items = capture_items("event", "span") - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) + client.post( + "/body/form", + data=BODY_FORM.encode("utf-8"), + headers={ + "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", + }, + ) - # Going forward, the sanitization of data will need to happen within the `before_send_span` hooks - # See https://sentry.slack.com/archives/C09RR0KD2N7/p1776951331206129?thread_ts=1776951227.440659&cid=C09RR0KD2N7 - parsed_form_attribute = json.loads( - server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] - ) - assert parsed_form_attribute.keys() == PARSED_FORM.keys() - assert parsed_form_attribute["username"] == PARSED_FORM["username"] - assert parsed_form_attribute["password"] == "hello123" - assert parsed_form_attribute["photo"] == "[Unparsable]" - else: - events = capture_events() - - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - }, - ) + (event,) = (item.payload for item in items if item.type == "event") + assert event["request"]["data"].keys() == PARSED_FORM.keys() + assert event["request"]["data"]["username"] == PARSED_FORM["username"] + assert event["request"]["data"]["password"] == "[Filtered]" + assert event["request"]["data"]["photo"] == "" - (event, transaction_event) = events - assert event["request"]["data"].keys() == PARSED_FORM.keys() - assert event["request"]["data"]["username"] == PARSED_FORM["username"] - assert event["request"]["data"]["password"] == "[Filtered]" - assert event["request"]["data"]["photo"] == "" - assert event["_meta"]["request"]["data"]["photo"] == { - "": {"rem": [["!raw", "x"]]} - } + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - assert transaction_event["request"]["data"].keys() == PARSED_FORM.keys() - assert ( - transaction_event["request"]["data"]["username"] == PARSED_FORM["username"] - ) - assert transaction_event["request"]["data"]["password"] == "[Filtered]" - assert transaction_event["request"]["data"]["photo"] == "" - assert transaction_event["_meta"]["request"]["data"]["photo"] == { - "": {"rem": [["!raw", "x"]]} - } + # Going forward, the sanitization of data will need to happen within the `before_send_span` hooks + # See https://sentry.slack.com/archives/C09RR0KD2N7/p1776951331206129?thread_ts=1776951227.440659&cid=C09RR0KD2N7 + parsed_form_attribute = json.loads( + server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] + ) + assert parsed_form_attribute.keys() == PARSED_FORM.keys() + assert parsed_form_attribute["username"] == PARSED_FORM["username"] + assert parsed_form_attribute["password"] == "hello123" + assert parsed_form_attribute["photo"] == "[Unparsable]" @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_request_body_too_big( - sentry_init, capture_events, capture_items, span_streaming -): +async def test_request_body_too_big(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) app = fastapi_app_factory() client = TestClient(app) - if span_streaming: - items = capture_items("event", "span") - - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) + items = capture_items("event", "span") - (event,) = (item.payload for item in items if item.type == "event") - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - # Because request is too big only the AnnotatedValue is extracted. - assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) + client.post( + "/body/form", + data=BODY_FORM.encode("utf-8"), + headers={ + "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", + "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", + }, + ) - # Because request is too big only the AnnotatedValue is extracted. - assert ( - server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] - == "[Exceeds maximum size]" - ) - else: - events = capture_events() - - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) + (event,) = (item.payload for item in items if item.type == "event") + assert event["request"]["cookies"] == { + "tasty_cookie": "strawberry", + "yummy_cookie": "choco", + } + # Because request is too big only the AnnotatedValue is extracted. + assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} - (event, transaction_event) = events - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - # Because request is too big only the AnnotatedValue is extracted. - assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - assert transaction_event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - # Because request is too big only the AnnotatedValue is extracted. - assert transaction_event["_meta"]["request"]["data"] == { - "": {"rem": [["!config", "x"]]} - } + # Because request is too big only the AnnotatedValue is extracted. + assert ( + server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] + == "[Exceeds maximum size]" + ) @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) async def test_formdata_request_body_data_collection_http_bodies_empty( - sentry_init, capture_events, capture_items, span_streaming + sentry_init, capture_items ): sentry_init( traces_sample_rate=1.0, max_request_body_size="always", integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments={"data_collection": {"http_bodies": []}}, ) @@ -365,31 +273,22 @@ async def test_formdata_request_body_data_collection_http_bodies_empty( headers = {"content-type": "multipart/form-data; boundary=fd721ef49ea403a6"} - if span_streaming: - items = capture_items("event", "span") + items = capture_items("event", "span") - client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) + client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) - (event,) = (item.payload for item in items if item.type == "event") - assert "data" not in event["request"] + (event,) = (item.payload for item in items if item.type == "event") + assert "data" not in event["request"] - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) - assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] - else: - events = capture_events() - - client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) - - (event, _) = events - assert "data" not in event["request"] + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) + assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "data_collection, expect_body", [ @@ -407,16 +306,14 @@ async def test_formdata_request_body_data_collection_http_bodies_empty( ) async def test_request_body_data_collection( sentry_init, - capture_events, capture_items, - span_streaming, data_collection, expect_body, ): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments=( {} if data_collection is None else {"data_collection": data_collection} ), @@ -425,50 +322,36 @@ async def test_request_body_data_collection( app = fastapi_app_factory() client = TestClient(app) - if span_streaming: - items = capture_items("event", "span") + items = capture_items("event", "span") - client.post("/body/json", json=BODY_JSON) + client.post("/body/json", json=BODY_JSON) - (event,) = (item.payload for item in items if item.type == "event") + (event,) = (item.payload for item in items if item.type == "event") - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - if expect_body: - assert event["request"]["data"] == BODY_JSON - assert ( - json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) - == BODY_JSON - ) - else: - assert "data" not in event["request"] - assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] + if expect_body: + assert event["request"]["data"] == BODY_JSON + assert ( + json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) + == BODY_JSON + ) else: - events = capture_events() - - client.post("/body/json", json=BODY_JSON) - - (event, _) = events - - if expect_body: - assert event["request"]["data"] == BODY_JSON - else: - assert "data" not in event["request"] + assert "data" not in event["request"] + assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] @pytest.mark.asyncio async def test_response(sentry_init, capture_events): - # FastAPI is heavily based on Starlette so we also need - # to enable StarletteIntegration. - # In the future this will be auto enabled. sentry_init( integrations=[StarletteIntegration(), FastApiIntegration()], traces_sample_rate=1.0, send_default_pii=True, + trace_lifecycle="stream", ) app = fastapi_app_factory() @@ -480,11 +363,11 @@ async def test_response(sentry_init, capture_events): assert response.json() == {"message": "Hi"} - assert len(events) == 2 + assert len(events) == 1 - (message_event, transaction_event) = events + (message_event,) = events assert message_event["message"] == "Hi" - assert transaction_event["transaction"] == "/message" + assert message_event["transaction"] == "/message" @pytest.mark.parametrize( @@ -571,7 +454,7 @@ def test_legacy_setup( @pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"]) -def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint): +def test_active_thread_id(sentry_init, capture_items, endpoint): sentry_init( auto_enabling_integrations=False, # Ensure httpx is not auto-enabled; its legacy start_span interferes with streaming mode integrations=[StarletteIntegration(), FastApiIntegration()], @@ -595,11 +478,8 @@ def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint): assert str(data["active"]) == segments[0]["attributes"]["thread.id"] -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_original_request_not_scrubbed( - sentry_init, capture_events, span_streaming -): +async def test_original_request_not_scrubbed(sentry_init, capture_events): sentry_init( auto_enabling_integrations=False, # Ensure httpx is not auto-enabled; its legacy start_span interferes with streaming mode integrations=[ @@ -608,7 +488,7 @@ async def test_original_request_not_scrubbed( LoggingIntegration(event_level=logging.ERROR), ], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) app = FastAPI() @@ -640,102 +520,6 @@ async def _error(request: Request): assert event["request"]["headers"]["proxy-authorization"] == "[Filtered]" -def test_response_status_code_ok_in_transaction_context(sentry_init, capture_envelopes): - """ - Tests that the response status code is added to the transaction "response" context. - """ - sentry_init( - integrations=[StarletteIntegration(), FastApiIntegration()], - traces_sample_rate=1.0, - release="demo-release", - ) - - envelopes = capture_envelopes() - - app = fastapi_app_factory() - - client = TestClient(app) - client.get("/message") - - (_, transaction_envelope) = envelopes - transaction = transaction_envelope.get_transaction_event() - - assert transaction["type"] == "transaction" - assert len(transaction["contexts"]) > 0 - assert "response" in transaction["contexts"].keys(), ( - "Response context not found in transaction" - ) - assert transaction["contexts"]["response"]["status_code"] == 200 - - -def test_response_status_code_error_in_transaction_context( - sentry_init, - capture_envelopes, -): - """ - Tests that the response status code is added to the transaction "response" context. - """ - sentry_init( - integrations=[StarletteIntegration(), FastApiIntegration()], - traces_sample_rate=1.0, - release="demo-release", - ) - - envelopes = capture_envelopes() - - app = fastapi_app_factory() - - client = TestClient(app) - with pytest.raises(ZeroDivisionError): - client.get("/error") - - ( - _, - _, - transaction_envelope, - ) = envelopes - transaction = transaction_envelope.get_transaction_event() - - assert transaction["type"] == "transaction" - assert len(transaction["contexts"]) > 0 - assert "response" in transaction["contexts"].keys(), ( - "Response context not found in transaction" - ) - assert transaction["contexts"]["response"]["status_code"] == 500 - - -def test_response_status_code_not_found_in_transaction_context( - sentry_init, - capture_envelopes, -): - """ - Tests that the response status code is added to the transaction "response" context. - """ - sentry_init( - integrations=[StarletteIntegration(), FastApiIntegration()], - traces_sample_rate=1.0, - release="demo-release", - ) - - envelopes = capture_envelopes() - - app = fastapi_app_factory() - - client = TestClient(app) - client.get("/non-existing-route-123") - - (transaction_envelope,) = envelopes - transaction = transaction_envelope.get_transaction_event() - - assert transaction["type"] == "transaction" - assert len(transaction["contexts"]) > 0 - assert "response" in transaction["contexts"].keys(), ( - "Response context not found in transaction" - ) - assert transaction["contexts"]["response"]["status_code"] == 404 - - -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "request_url,transaction_style,expected_transaction_name,expected_transaction_source", [ @@ -759,9 +543,7 @@ def test_transaction_name( transaction_style, expected_transaction_name, expected_transaction_source, - capture_envelopes, capture_items, - span_streaming, ): """ Tests that the transaction name is something meaningful. @@ -773,46 +555,30 @@ def test_transaction_name( FastApiIntegration(transaction_style=transaction_style), ], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") - else: - envelopes = capture_envelopes() + items = capture_items("span") app = fastapi_app_factory() client = TestClient(app) client.get(request_url) - if span_streaming: - sentry_sdk.flush() - segments = [item.payload for item in items if item.payload.get("is_segment")] - assert len(segments) == 1 - segment = segments[0] - assert segment["name"] == expected_transaction_name - assert ( - segment["attributes"]["sentry.segment.name.source"] - == expected_transaction_source - ) - else: - (_, transaction_envelope) = envelopes - transaction_event = transaction_envelope.get_transaction_event() - - assert transaction_event["transaction"] == expected_transaction_name - assert ( - transaction_event["transaction_info"]["source"] - == expected_transaction_source - ) + sentry_sdk.flush() + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + segment = segments[0] + assert segment["name"] == expected_transaction_name + assert ( + segment["attributes"]["sentry.segment.name.source"] + == expected_transaction_source + ) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_transaction_name_with_prefix( sentry_init, - capture_envelopes, capture_items, - span_streaming, ): sentry_init( auto_enabling_integrations=False, @@ -821,13 +587,10 @@ def test_transaction_name_with_prefix( FastApiIntegration(transaction_style="url"), ], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) - if span_streaming: - items = capture_items("span") - else: - envelopes = capture_envelopes() + items = capture_items("span") app = FastAPI() router = APIRouter() @@ -841,18 +604,12 @@ async def get_user(user_id: int): client = TestClient(app) client.get("/api/users/123") - if span_streaming: - sentry_sdk.flush() - segments = [item.payload for item in items if item.payload.get("is_segment")] - assert len(segments) == 1 - segment = segments[0] - assert segment["name"] == "/api/users/{user_id}" - assert segment["attributes"]["sentry.segment.name.source"] == "route" - else: - (transaction_envelope,) = envelopes - transaction_event = transaction_envelope.get_transaction_event() - assert transaction_event["transaction"] == "/api/users/{user_id}" - assert transaction_event["transaction_info"]["source"] == "route" + sentry_sdk.flush() + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + segment = segments[0] + assert segment["name"] == "/api/users/{user_id}" + assert segment["attributes"]["sentry.segment.name.source"] == "route" def test_route_endpoint_equal_dependant_call(sentry_init): @@ -866,6 +623,7 @@ def test_route_endpoint_equal_dependant_call(sentry_init): FastApiIntegration(), ], traces_sample_rate=1.0, + trace_lifecycle="stream", ) app = fastapi_app_factory() @@ -919,6 +677,7 @@ def dummy_traces_sampler(sampling_context): integrations=[StarletteIntegration(transaction_style=transaction_style)], traces_sampler=dummy_traces_sampler, traces_sample_rate=1.0, + trace_lifecycle="stream", ) app = fastapi_app_factory() @@ -952,7 +711,7 @@ def test_transaction_name_in_middleware( transaction_style, expected_transaction_name, expected_transaction_source, - capture_envelopes, + capture_items, ): """ Tests that the transaction name is something meaningful. @@ -968,9 +727,10 @@ def test_transaction_name_in_middleware( ), ], traces_sample_rate=1.0, + trace_lifecycle="stream", ) - envelopes = capture_envelopes() + items = capture_items("span") app = fastapi_app_factory() @@ -984,13 +744,14 @@ def test_transaction_name_in_middleware( client = TestClient(app) client.get(request_url) - (transaction_envelope,) = envelopes - transaction_event = transaction_envelope.get_transaction_event() - - assert transaction_event["contexts"]["response"]["status_code"] == 400 - assert transaction_event["transaction"] == expected_transaction_name + sentry_sdk.flush() + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + segment = segments[0] + assert segment["name"] == expected_transaction_name assert ( - transaction_event["transaction_info"]["source"] == expected_transaction_source + segment["attributes"]["sentry.segment.name.source"] + == expected_transaction_source ) @@ -998,46 +759,42 @@ def test_transaction_name_in_middleware( FASTAPI_VERSION < (0, 80), reason="Requires FastAPI >= 0.80, because earlier versions do not support HTTP 'HEAD' requests", ) -def test_transaction_http_method_default(sentry_init, capture_events): +def test_transaction_http_method_default(sentry_init, capture_items): """ - By default OPTIONS and HEAD requests do not create a transaction. + By default OPTIONS and HEAD requests do not create a span. """ - # FastAPI is heavily based on Starlette so we also need - # to enable StarletteIntegration. - # In the future this will be auto enabled. sentry_init( + auto_enabling_integrations=False, traces_sample_rate=1.0, integrations=[ StarletteIntegration(), FastApiIntegration(), ], + trace_lifecycle="stream", ) app = fastapi_app_factory() - events = capture_events() + items = capture_items("span") client = TestClient(app) client.get("/nomessage") client.options("/nomessage") client.head("/nomessage") - assert len(events) == 1 - - (event,) = events - - assert event["request"]["method"] == "GET" + sentry_sdk.flush() + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + assert segments[0]["attributes"]["http.request.method"] == "GET" @pytest.mark.skipif( FASTAPI_VERSION < (0, 80), reason="Requires FastAPI >= 0.80, because earlier versions do not support HTTP 'HEAD' requests", ) -def test_transaction_http_method_custom(sentry_init, capture_events): - # FastAPI is heavily based on Starlette so we also need - # to enable StarletteIntegration. - # In the future this will be auto enabled. +def test_transaction_http_method_custom(sentry_init, capture_items): sentry_init( + auto_enabling_integrations=False, traces_sample_rate=1.0, integrations=[ StarletteIntegration( @@ -1053,63 +810,53 @@ def test_transaction_http_method_custom(sentry_init, capture_events): ), # capitalization does not matter ), ], + trace_lifecycle="stream", ) app = fastapi_app_factory() - events = capture_events() + items = capture_items("span") client = TestClient(app) client.get("/nomessage") client.options("/nomessage") client.head("/nomessage") - assert len(events) == 2 - - (event1, event2) = events + sentry_sdk.flush() + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 2 - assert event1["request"]["method"] == "OPTIONS" - assert event2["request"]["method"] == "HEAD" + assert segments[0]["attributes"]["http.request.method"] == "OPTIONS" + assert segments[1]["attributes"]["http.request.method"] == "HEAD" -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_request_url(sentry_init, capture_events, capture_items, span_streaming): +def test_request_url(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, integrations=[ StarletteIntegration(), ], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = fastapi_app_factory() client = TestClient(starlette_app) - if span_streaming: - items = capture_items("span") - - client.get("/root/nomessage") - sentry_sdk.flush() - spans = [item.payload for item in items] - - (server_span,) = ( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.server" - ) - assert server_span["attributes"][SPANDATA.URL_FULL] == ( - "http://testserver/root/nomessage" - ) - assert server_span["attributes"][SPANDATA.URL_PATH] == "/root/nomessage" - else: - events = capture_events() + items = capture_items("span") - client.get("/root/nomessage") + client.get("/root/nomessage") + sentry_sdk.flush() + spans = [item.payload for item in items] - (event,) = events - assert event["request"]["url"] == "http://testserver/root/nomessage" + (server_span,) = ( + span for span in spans if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"][SPANDATA.URL_FULL] == ( + "http://testserver/root/nomessage" + ) + assert server_span["attributes"][SPANDATA.URL_PATH] == "/root/nomessage" @parametrize_test_configurable_status_codes @@ -1146,13 +893,14 @@ async def _error(): @pytest.mark.parametrize("transaction_style", ["endpoint", "url"]) -def test_app_host(sentry_init, capture_events, transaction_style): +def test_app_host(sentry_init, capture_items, transaction_style): sentry_init( traces_sample_rate=1.0, integrations=[ StarletteIntegration(transaction_style=transaction_style), FastApiIntegration(transaction_style=transaction_style), ], + trace_lifecycle="stream", ) app = FastAPI() @@ -1164,20 +912,20 @@ async def subapp_route(): app.host("subapp", subapp) - events = capture_events() + items = capture_items("span") client = TestClient(app) client.get("/subapp", headers={"Host": "subapp"}) - assert len(events) == 1 - - (event,) = events - assert "transaction" in event + sentry_sdk.flush() + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + segment = segments[0] if transaction_style == "url": - assert event["transaction"] == "/subapp" + assert segment["name"] == "/subapp" else: - assert event["transaction"].endswith("subapp_route") + assert segment["name"].endswith("subapp_route") @pytest.mark.asyncio @@ -1185,6 +933,7 @@ async def test_feature_flags(sentry_init, capture_events): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration(), FastApiIntegration()], + trace_lifecycle="stream", ) events = capture_events() @@ -1195,8 +944,8 @@ async def test_feature_flags(sentry_init, capture_events): async def _error(): add_feature_flag("hello", False) - with sentry_sdk.start_span(name="test-span"): - with sentry_sdk.start_span(name="test-span-2"): + with sentry_sdk.traces.start_span(name="test-span"): + with sentry_sdk.traces.start_span(name="test-span-2"): raise ValueError("something is wrong!") try: From 70631fb7564943e57dcce02baf06acb655cdcca2 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 2 Sep 2026 14:50:05 +0200 Subject: [PATCH 2/2] chore(starlette): Drop transactions --- sentry_sdk/integrations/starlette.py | 45 +- .../integrations/starlette/test_starlette.py | 734 +++++++----------- 2 files changed, 306 insertions(+), 473 deletions(-) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 2bedf32ce2..19a903aeb8 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -25,12 +25,11 @@ ) from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.traces import StreamedSpan, get_current_span +from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing import ( SOURCE_FOR_STYLE, TransactionSource, ) -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( AnnotatedValue, capture_internal_exceptions, @@ -170,24 +169,18 @@ async def _create_span_call( return await old_call(app, scope, receive, send, **kwargs) middleware_name = app.__class__.__name__ - is_span_streaming_enabled = has_span_streaming_enabled(client.options) def _start_middleware_span(op: str, name: str) -> "Any": - if is_span_streaming_enabled: - if sentry_sdk.traces.get_current_span() is None: - return nullcontext() - return sentry_sdk.traces.start_span( - name=name, - attributes={ - "sentry.op": op, - "sentry.origin": StarletteIntegration.origin, - "middleware.name": middleware_name, - }, - ) - return sentry_sdk.start_span( - op=op, + if sentry_sdk.traces.get_current_span() is None: + return nullcontext() + + return sentry_sdk.traces.start_span( name=name, - origin=StarletteIntegration.origin, + attributes={ + "sentry.op": op, + "sentry.origin": StarletteIntegration.origin, + "middleware.name": middleware_name, + }, ) with _start_middleware_span(op=OP.MIDDLEWARE_STARLETTE, name=middleware_name): @@ -549,7 +542,7 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": try: return await handler(*args, **kwargs) finally: - current_span = get_current_span() + current_span = sentry_sdk.traces.get_current_span() if type(current_span) is StreamedSpan: attach_request_data = True @@ -594,23 +587,19 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any": if integration is None: return old_func(*args, **kwargs) - current_scope = sentry_sdk.get_current_scope() - - span_streaming = has_span_streaming_enabled(client.options) - if span_streaming: - current_span = current_scope.streamed_span + current_span = sentry_sdk.traces.get_current_span() - if type(current_span) is StreamedSpan: - current_span._segment._update_active_thread() - elif current_scope.transaction is not None: - current_scope.transaction.update_active_thread() + if type(current_span) is StreamedSpan: + current_span._segment._update_active_thread() sentry_scope = sentry_sdk.get_isolation_scope() request = args[0] _set_transaction_name_and_source( - current_scope, integration.transaction_style, request + sentry_sdk.get_current_scope(), + integration.transaction_style, + request, ) extractor = StarletteRequestExtractor(request) diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py index d60869ac4d..99986b436a 100644 --- a/tests/integrations/starlette/test_starlette.py +++ b/tests/integrations/starlette/test_starlette.py @@ -288,238 +288,146 @@ async def my_send(*args, **kwargs): @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_request_info_json_body( - sentry_init, capture_events, capture_items, span_streaming -): +async def test_request_info_json_body(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory() client = TestClient(starlette_app) - if span_streaming: - items = capture_items("event", "span") - - client.post( - "/body/json", - json=BODY_JSON, - headers={ - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) - - (event,) = (item.payload for item in items if item.type == "event") - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - assert event["request"]["data"] == BODY_JSON - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) - - assert json.loads( - server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] - ) == {"some": "json", "for": "testing", "nested": {"numbers": 123}} - else: - events = capture_events() + items = capture_items("event", "span") - client.post( - "/body/json", - json=BODY_JSON, - headers={ - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) + client.post( + "/body/json", + json=BODY_JSON, + headers={ + "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", + }, + ) - (event, transaction_event) = events + (event,) = (item.payload for item in items if item.type == "event") + assert event["request"]["cookies"] == { + "tasty_cookie": "strawberry", + "yummy_cookie": "choco", + } + assert event["request"]["data"] == BODY_JSON - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - assert event["request"]["data"] == BODY_JSON + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - assert transaction_event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - assert transaction_event["request"]["data"] == BODY_JSON + assert json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) == { + "some": "json", + "for": "testing", + "nested": {"numbers": 123}, + } @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_formdata_request_body( - sentry_init, capture_events, capture_items, span_streaming -): +async def test_formdata_request_body(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, max_request_body_size="always", integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory() client = TestClient(starlette_app) - if span_streaming: - items = capture_items("event", "span") + items = capture_items("event", "span") - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - }, - ) - - (event,) = (item.payload for item in items if item.type == "event") - assert event["request"]["data"].keys() == PARSED_FORM.keys() - assert event["request"]["data"]["username"] == PARSED_FORM["username"] - assert event["request"]["data"]["password"] == "[Filtered]" - assert event["request"]["data"]["photo"] == "" - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) - - # Going forward, the sanitization of data will need to happen within the `before_send_span` hooks - # See https://sentry.slack.com/archives/C09RR0KD2N7/p1776951331206129?thread_ts=1776951227.440659&cid=C09RR0KD2N7 - parsed_form_attribute = json.loads( - server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] - ) - assert parsed_form_attribute.keys() == PARSED_FORM.keys() - assert parsed_form_attribute["username"] == PARSED_FORM["username"] - assert parsed_form_attribute["password"] == "hello123" - assert parsed_form_attribute["photo"] == "[Unparsable]" - else: - events = capture_events() + client.post( + "/body/form", + data=BODY_FORM.encode("utf-8"), + headers={ + "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", + }, + ) - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - }, - ) + (event,) = (item.payload for item in items if item.type == "event") + assert event["request"]["data"].keys() == PARSED_FORM.keys() + assert event["request"]["data"]["username"] == PARSED_FORM["username"] + assert event["request"]["data"]["password"] == "[Filtered]" + assert event["request"]["data"]["photo"] == "" - (event, transaction_event) = events - assert event["request"]["data"].keys() == PARSED_FORM.keys() - assert event["request"]["data"]["username"] == PARSED_FORM["username"] - assert event["request"]["data"]["password"] == "[Filtered]" - assert event["request"]["data"]["photo"] == "" - assert event["_meta"]["request"]["data"]["photo"] == { - "": {"rem": [["!raw", "x"]]} - } + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - assert transaction_event["request"]["data"].keys() == PARSED_FORM.keys() - assert ( - transaction_event["request"]["data"]["username"] == PARSED_FORM["username"] - ) - assert transaction_event["request"]["data"]["password"] == "[Filtered]" - assert transaction_event["request"]["data"]["photo"] == "" - assert transaction_event["_meta"]["request"]["data"]["photo"] == { - "": {"rem": [["!raw", "x"]]} - } + # Going forward, the sanitization of data will need to happen within the `before_send_span` hooks + # See https://sentry.slack.com/archives/C09RR0KD2N7/p1776951331206129?thread_ts=1776951227.440659&cid=C09RR0KD2N7 + parsed_form_attribute = json.loads( + server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] + ) + assert parsed_form_attribute.keys() == PARSED_FORM.keys() + assert parsed_form_attribute["username"] == PARSED_FORM["username"] + assert parsed_form_attribute["password"] == "hello123" + assert parsed_form_attribute["photo"] == "[Unparsable]" @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_request_body_too_big( - sentry_init, capture_events, capture_items, span_streaming -): +async def test_request_body_too_big(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory() client = TestClient(starlette_app) - if span_streaming: - items = capture_items("event", "span") + items = capture_items("event", "span") - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) - - (event,) = (item.payload for item in items if item.type == "event") - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - # Because request is too big only the AnnotatedValue is extracted. - assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) + client.post( + "/body/form", + data=BODY_FORM.encode("utf-8"), + headers={ + "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", + "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", + }, + ) - # Because request is too big only the AnnotatedValue is extracted. - assert ( - server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] - == "[Exceeds maximum size]" - ) - else: - events = capture_events() - - client.post( - "/body/form", - data=BODY_FORM.encode("utf-8"), - headers={ - "content-type": "multipart/form-data; boundary=fd721ef49ea403a6", - "cookie": "yummy_cookie=choco; tasty_cookie=strawberry", - }, - ) + (event,) = (item.payload for item in items if item.type == "event") + assert event["request"]["cookies"] == { + "tasty_cookie": "strawberry", + "yummy_cookie": "choco", + } + # Because request is too big only the AnnotatedValue is extracted. + assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} - (event, transaction_event) = events - assert event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - # Because request is too big only the AnnotatedValue is extracted. - assert event["_meta"]["request"]["data"] == {"": {"rem": [["!config", "x"]]}} + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - assert transaction_event["request"]["cookies"] == { - "tasty_cookie": "strawberry", - "yummy_cookie": "choco", - } - # Because request is too big only the AnnotatedValue is extracted. - assert transaction_event["_meta"]["request"]["data"] == { - "": {"rem": [["!config", "x"]]} - } + # Because request is too big only the AnnotatedValue is extracted. + assert ( + server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA] + == "[Exceeds maximum size]" + ) @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) async def test_formdata_request_body_data_collection_http_bodies_empty( - sentry_init, capture_events, capture_items, span_streaming + sentry_init, capture_items ): sentry_init( traces_sample_rate=1.0, max_request_body_size="always", integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments={"data_collection": {"http_bodies": []}}, ) @@ -528,31 +436,22 @@ async def test_formdata_request_body_data_collection_http_bodies_empty( headers = {"content-type": "multipart/form-data; boundary=fd721ef49ea403a6"} - if span_streaming: - items = capture_items("event", "span") + items = capture_items("event", "span") - client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) + client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) - (event,) = (item.payload for item in items if item.type == "event") - assert "data" not in event["request"] + (event,) = (item.payload for item in items if item.type == "event") + assert "data" not in event["request"] - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) - assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] - else: - events = capture_events() - - client.post("/body/form", data=BODY_FORM.encode("utf-8"), headers=headers) - - (event, _) = events - assert "data" not in event["request"] + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) + assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] @pytest.mark.asyncio -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "data_collection, expect_body", [ @@ -570,16 +469,14 @@ async def test_formdata_request_body_data_collection_http_bodies_empty( ) async def test_request_body_data_collection( sentry_init, - capture_events, capture_items, - span_streaming, data_collection, expect_body, ): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", _experiments=( {} if data_collection is None else {"data_collection": data_collection} ), @@ -588,51 +485,41 @@ async def test_request_body_data_collection( starlette_app = starlette_app_factory() client = TestClient(starlette_app) - if span_streaming: - items = capture_items("event", "span") + items = capture_items("event", "span") - client.post("/body/json", json=BODY_JSON) + client.post("/body/json", json=BODY_JSON) - (event,) = (item.payload for item in items if item.type == "event") + (event,) = (item.payload for item in items if item.type == "event") - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - server_span = next( - span for span in spans if span["attributes"]["sentry.op"] == "http.server" - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) - if expect_body: - assert event["request"]["data"] == BODY_JSON - assert ( - json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) - == BODY_JSON - ) - else: - assert "data" not in event["request"] - assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] + if expect_body: + assert event["request"]["data"] == BODY_JSON + assert ( + json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) + == BODY_JSON + ) else: - events = capture_events() - - client.post("/body/json", json=BODY_JSON) - - (event, _) = events - - if expect_body: - assert event["request"]["data"] == BODY_JSON - else: - assert "data" not in event["request"] + assert "data" not in event["request"] + assert SPANDATA.HTTP_REQUEST_BODY_DATA not in server_span["attributes"] @pytest.mark.asyncio -async def test_request_info_no_pii(sentry_init, capture_events): +async def test_request_info_no_pii(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=False, integrations=[StarletteIntegration()], + trace_lifecycle="stream", ) starlette_app = starlette_app_factory() - events = capture_events() + + items = capture_items("event", "span") client = TestClient(starlette_app) client.post( @@ -643,12 +530,19 @@ async def test_request_info_no_pii(sentry_init, capture_events): }, ) - (event, transaction_event) = events + (event,) = (item.payload for item in items if item.type == "event") assert "cookies" not in event["request"] assert event["request"]["data"] == BODY_JSON - assert "cookies" not in transaction_event["request"] - assert transaction_event["request"]["data"] == BODY_JSON + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + server_span = next( + span for span in spans if span["attributes"]["sentry.op"] == "http.server" + ) + assert ( + json.loads(server_span["attributes"][SPANDATA.HTTP_REQUEST_BODY_DATA]) + == BODY_JSON + ) @pytest.mark.asyncio @@ -759,6 +653,7 @@ async def test_cookie_data_collection( sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], + trace_lifecycle="stream", **init_kwargs, ) @@ -768,14 +663,12 @@ async def test_cookie_data_collection( client = TestClient(starlette_app) client.get("/message", headers={"cookie": COOKIE_HEADER}) - (event, transaction_event) = events + (event,) = events if expected_cookies is None: assert "cookies" not in event["request"] - assert "cookies" not in transaction_event["request"] else: assert event["request"]["cookies"] == expected_cookies - assert transaction_event["request"]["cookies"] == expected_cookies @pytest.mark.parametrize( @@ -819,6 +712,7 @@ def test_query_string_data_collection( sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], + trace_lifecycle="stream", **init_kwargs, ) @@ -828,14 +722,12 @@ def test_query_string_data_collection( client = TestClient(starlette_app) client.get("/message?" + QUERY_STRING) - (event, transaction_event) = events + (event,) = events if expected_query_string is None: assert "query_string" not in event["request"] - assert "query_string" not in transaction_event["request"] else: assert event["request"]["query_string"] == expected_query_string - assert transaction_event["request"]["query_string"] == expected_query_string @pytest.mark.parametrize( @@ -945,32 +837,6 @@ def test_span_http_query_data_collection( @pytest.mark.parametrize("init_kwargs, expected_ip", USER_INFO_CASES) def test_user_info_data_collection( - sentry_init, capture_events, init_kwargs, expected_ip -): - sentry_init( - traces_sample_rate=1.0, - integrations=[StarletteIntegration()], - **init_kwargs, - ) - - starlette_app = starlette_app_factory() - events = capture_events() - - client = TestClient(starlette_app) - client.get("/message") - - (event, transaction_event) = events - - if expected_ip is NO_USER_INFO: - assert "env" not in event["request"] - assert "env" not in transaction_event["request"] - else: - assert event["request"]["env"] == {"REMOTE_ADDR": expected_ip} - assert transaction_event["request"]["env"] == {"REMOTE_ADDR": expected_ip} - - -@pytest.mark.parametrize("init_kwargs, expected_ip", USER_INFO_CASES) -def test_user_info_data_collection_with_streamed_spans( sentry_init, capture_items, init_kwargs, expected_ip ): kwargs = dict(init_kwargs) @@ -1051,13 +917,16 @@ def test_transaction_style( assert event["transaction_info"] == {"source": expected_source} -def test_host_route_path_has_url_source(sentry_init, capture_events): +def test_host_route_path_has_url_source(sentry_init, capture_items): sentry_init( + auto_enabling_integrations=False, integrations=[StarletteIntegration(transaction_style="url")], traces_sample_rate=1.0, + trace_lifecycle="stream", ) async def hosted_endpoint(request): + sentry_sdk.capture_message("hi") return starlette.responses.JSONResponse({"status": "ok"}) subapp = starlette.applications.Starlette( @@ -1067,13 +936,24 @@ async def hosted_endpoint(request): routes=[starlette.routing.Host("subapp", subapp)] ) - events = capture_events() + items = capture_items("event", "span") client = TestClient(app) client.get("/users/123456", headers={"Host": "subapp"}) - (event,) = events - assert event["transaction"].endswith("/users/123456") - assert event["transaction_info"] == {"source": "url"} + sentry_sdk.flush() + + events = [item.payload for item in items if item.type == "event"] + assert len(events) == 1 + assert events[0]["transaction"].endswith("/users/123456") + + segments = [ + item.payload + for item in items + if item.type == "span" and item.payload.get("is_segment") + ] + assert len(segments) == 1 + assert segments[0]["name"].endswith("/users/123456") + assert segments[0]["attributes"]["sentry.segment.name.source"] == "url" @pytest.mark.parametrize( @@ -1167,12 +1047,11 @@ def test_user_information_error(sentry_init, capture_events, init_kwargs, expect @pytest.mark.parametrize("init_kwargs, expect_user", USER_AUTH_CASES) -def test_user_information_transaction( - sentry_init, capture_events, init_kwargs, expect_user -): +def test_user_information(sentry_init, capture_events, init_kwargs, expect_user): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], + trace_lifecycle="stream", **init_kwargs, ) starlette_app = starlette_app_factory( @@ -1183,14 +1062,14 @@ def test_user_information_transaction( client = TestClient(starlette_app, raise_server_exceptions=False) client.get("/message", auth=("Gabriela", "hello123")) - (_, transaction_event) = events + (event,) = events if expect_user: - user = transaction_event.get("user", None) + user = event.get("user", None) assert user assert "username" in user assert user["username"] == "Gabriela" else: - assert "user" not in transaction_event + assert "user" not in event def test_user_information_does_not_clobber_app_set_user(sentry_init, capture_events): @@ -1205,6 +1084,7 @@ def test_user_information_does_not_clobber_app_set_user(sentry_init, capture_eve traces_sample_rate=1.0, send_default_pii=True, integrations=[StarletteIntegration()], + trace_lifecycle="stream", ) async def _set_user(request): @@ -1215,6 +1095,7 @@ async def _set_user(request): "username": "Ada", } ) + capture_message("hi") return starlette.responses.JSONResponse({"status": "ok"}) app = starlette.applications.Starlette( @@ -1227,30 +1108,26 @@ async def _set_user(request): client = TestClient(app, raise_server_exceptions=False) client.get("/set_user", auth=("Ada", "hello123")) - (transaction_event,) = events - user = transaction_event.get("user", None) + (event,) = events + user = event.get("user", None) assert user assert user["username"] == "Ada" assert user["id"] == "user_42" assert user["email"] == "ada@beans.com" -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_middleware_spans(sentry_init, capture_events, capture_items, span_streaming): +def test_middleware_spans(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration(middleware_spans=True)], auto_enabling_integrations=False, # disable because httpx will enable otherwise, leading to the segment span being an `http.client` sentry.op (the TestClient initiating the request), rather than the more realistic `http.server`. - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory( middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] ) - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") client = TestClient(starlette_app, raise_server_exceptions=False) try: @@ -1268,56 +1145,37 @@ def test_middleware_spans(sentry_init, capture_events, capture_items, span_strea "ServerErrorMiddleware", # 'op': 'middleware.starlette.send' ] - if span_streaming: - sentry_sdk.flush() - - segment = items.pop().payload - middleware_spans = [item.payload for item in items] - - # In span-first, the `middleware.starlette.send` ops appear first, - # so the list needs to be reversed for the assertions below - middleware_spans.reverse() + sentry_sdk.flush() - assert len(middleware_spans) == len(expected_middleware_spans) + segment = items.pop().payload + middleware_spans = [item.payload for item in items] - assert segment["is_segment"] is True - assert segment["attributes"]["sentry.op"] == "http.server" + # In span-first, the `middleware.starlette.send` ops appear first, + # so the list needs to be reversed for the assertions below + middleware_spans.reverse() - idx = 0 - for idx, span in enumerate(middleware_spans): - assert ( - span["attributes"]["middleware.name"] == expected_middleware_spans[idx] - ) - else: - (_, transaction_event) = events + assert len(middleware_spans) == len(expected_middleware_spans) - assert len(transaction_event["spans"]) == len(expected_middleware_spans) + assert segment["is_segment"] is True + assert segment["attributes"]["sentry.op"] == "http.server" - idx = 0 - for span in transaction_event["spans"]: - if span["op"] == "middleware.starlette": - assert span["description"] == expected_middleware_spans[idx] - idx += 1 + idx = 0 + for idx, span in enumerate(middleware_spans): + assert span["attributes"]["middleware.name"] == expected_middleware_spans[idx] -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_middleware_spans_disabled( - sentry_init, capture_events, capture_items, span_streaming -): +def test_middleware_spans_disabled(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration(middleware_spans=False)], auto_enabling_integrations=False, # disable because httpx will enable otherwise, leading to the segment span being an `http.client` sentry.op (the TestClient initiating the request), rather than the more realistic `http.server`. - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory( middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] ) - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") client = TestClient(starlette_app, raise_server_exceptions=False) try: @@ -1325,37 +1183,27 @@ def test_middleware_spans_disabled( except Exception: pass - if span_streaming: - sentry_sdk.flush() + sentry_sdk.flush() - segment = items.pop().payload - middleware_spans = [item.payload for item in items] + segment = items.pop().payload + middleware_spans = [item.payload for item in items] - assert len(middleware_spans) == 0 + assert len(middleware_spans) == 0 - assert segment["is_segment"] is True - assert segment["attributes"]["sentry.op"] == "http.server" - else: - (_, transaction_event) = events - assert len(transaction_event["spans"]) == 0 + assert segment["is_segment"] is True + assert segment["attributes"]["sentry.op"] == "http.server" -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_middleware_callback_spans( - sentry_init, capture_events, capture_items, span_streaming -): +def test_middleware_callback_spans(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration(middleware_spans=True)], auto_enabling_integrations=False, # disable because httpx will enable otherwise, leading to the segment span being an `http.client` sentry.op (the TestClient initiating the request), rather than the more realistic `http.server`. - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory(middleware=[Middleware(SampleMiddleware)]) - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") client = TestClient(starlette_app, raise_server_exceptions=False) try: @@ -1411,39 +1259,31 @@ def test_middleware_callback_spans( }, ] - if span_streaming: - sentry_sdk.flush() - - segment = items.pop().payload - middleware_spans = [item.payload for item in items] + sentry_sdk.flush() - # In span-first, the `middleware.starlette.send` ops appear first, - # so the list needs to be reversed for the assertions below - middleware_spans.reverse() + segment = items.pop().payload + middleware_spans = [item.payload for item in items] - assert len(middleware_spans) == len(expected) + # In span-first, the `middleware.starlette.send` ops appear first, + # so the list needs to be reversed for the assertions below + middleware_spans.reverse() - assert segment["is_segment"] is True - assert segment["attributes"]["sentry.op"] == "http.server" + assert len(middleware_spans) == len(expected) - for span, exp in zip(middleware_spans, expected): - assert span["attributes"]["sentry.op"] == exp["op"] - assert span["name"] == exp["description"] - assert span["attributes"]["middleware.name"] == exp["middleware_name"] - else: - (_, transaction_event) = events + assert segment["is_segment"] is True + assert segment["attributes"]["sentry.op"] == "http.server" - idx = 0 - for span in transaction_event["spans"]: - assert span["op"] == expected[idx]["op"] - assert span["description"] == expected[idx]["description"] - idx += 1 + for span, exp in zip(middleware_spans, expected): + assert span["attributes"]["sentry.op"] == exp["op"] + assert span["name"] == exp["description"] + assert span["attributes"]["middleware.name"] == exp["middleware_name"] def test_middleware_receive_send(sentry_init, capture_events): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], + trace_lifecycle="stream", ) starlette_app = starlette_app_factory( middleware=[Middleware(SampleReceiveSendMiddleware)] @@ -1458,15 +1298,17 @@ def test_middleware_receive_send(sentry_init, capture_events): pass -def test_middleware_partial_receive_send(sentry_init, capture_events): +def test_middleware_partial_receive_send(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], + auto_enabling_integrations=False, + trace_lifecycle="stream", ) starlette_app = starlette_app_factory( middleware=[Middleware(SamplePartialReceiveSendMiddleware)] ) - events = capture_events() + items = capture_items("span") client = TestClient(starlette_app, raise_server_exceptions=False) try: @@ -1474,7 +1316,14 @@ def test_middleware_partial_receive_send(sentry_init, capture_events): except Exception: pass - (_, transaction_event) = events + sentry_sdk.flush() + + segment = items.pop().payload + middleware_spans = [item.payload for item in items] + + # In span-first, the `middleware.starlette.send` ops appear first, + # so the list needs to be reversed for the assertions below + middleware_spans.reverse() expected = [ { @@ -1515,11 +1364,12 @@ def test_middleware_partial_receive_send(sentry_init, capture_events): }, ] - idx = 0 - for span in transaction_event["spans"]: - assert span["op"] == expected[idx]["op"] - assert span["description"].startswith(expected[idx]["description"]) - idx += 1 + assert segment["is_segment"] is True + assert segment["attributes"]["sentry.op"] == "http.server" + + for span, exp in zip(middleware_spans, expected): + assert span["attributes"]["sentry.op"] == exp["op"] + assert span["name"].startswith(exp["description"]) @pytest.mark.skipif( @@ -1530,6 +1380,7 @@ def test_middleware_positional_args(sentry_init): sentry_init( traces_sample_rate=1.0, integrations=[StarletteIntegration()], + trace_lifecycle="stream", ) _ = starlette_app_factory(middleware=[Middleware(SampleMiddlewareWithArgs, "bla")]) @@ -1559,7 +1410,7 @@ def test_legacy_setup( @pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"]) -def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint): +def test_active_thread_id(sentry_init, capture_items, endpoint): sentry_init( auto_enabling_integrations=False, # avoid legacy spans from auto-enabled integrations leaking into streaming mode integrations=[StarletteIntegration()], @@ -1610,22 +1461,27 @@ def test_segment_name_is_route_resolved_name_span_streaming( @pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"]) -def test_transaction_name_is_route_resolved_name_static( - sentry_init, capture_events, endpoint +def test_segment_name_is_route_resolved_name_static( + sentry_init, capture_items, endpoint ): sentry_init( + auto_enabling_integrations=False, integrations=[StarletteIntegration(transaction_style="url")], traces_sample_rate=1.0, + trace_lifecycle="stream", ) - events = capture_events() + items = capture_items("span") client = TestClient(starlette_app_factory()) response = client.get(endpoint) assert response.status_code == 200 - (transaction,) = [e for e in events if e.get("type") == "transaction"] - assert transaction["transaction"] == endpoint - assert transaction["transaction_info"] == {"source": "route"} + sentry_sdk.flush() + + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + assert segments[0]["name"] == endpoint + assert segments[0]["attributes"]["sentry.segment.name.source"] == "route" def test_original_request_not_scrubbed(sentry_init, capture_events): @@ -1720,7 +1576,7 @@ def test_transaction_name( transaction_style, expected_transaction_name, expected_transaction_source, - capture_envelopes, + capture_items, ): """ Tests that the transaction name is something meaningful. @@ -1729,20 +1585,23 @@ def test_transaction_name( auto_enabling_integrations=False, # Make sure that httpx integration is not added, because it adds tracing information to the starlette test clients request. integrations=[StarletteIntegration(transaction_style=transaction_style)], traces_sample_rate=1.0, + trace_lifecycle="stream", ) - envelopes = capture_envelopes() + items = capture_items("span") app = starlette_app_factory() client = TestClient(app) client.get(request_url) - (_, transaction_envelope) = envelopes - transaction_event = transaction_envelope.get_transaction_event() + sentry_sdk.flush() - assert transaction_event["transaction"] == expected_transaction_name + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + assert segments[0]["name"] == expected_transaction_name assert ( - transaction_event["transaction_info"]["source"] == expected_transaction_source + segments[0]["attributes"]["sentry.segment.name.source"] + == expected_transaction_source ) @@ -1789,6 +1648,7 @@ def dummy_traces_sampler(sampling_context): integrations=[StarletteIntegration(transaction_style=transaction_style)], traces_sampler=dummy_traces_sampler, traces_sample_rate=1.0, + trace_lifecycle="stream", ) app = starlette_app_factory() @@ -1821,7 +1681,7 @@ def test_transaction_name_in_middleware( transaction_style, expected_transaction_name, expected_transaction_source, - capture_envelopes, + capture_items, ): """ Tests that the transaction name is something meaningful. @@ -1834,9 +1694,10 @@ def test_transaction_name_in_middleware( ), ], traces_sample_rate=1.0, + trace_lifecycle="stream", ) - envelopes = capture_envelopes() + items = capture_items("span") middleware = [ Middleware( @@ -1849,32 +1710,29 @@ def test_transaction_name_in_middleware( client = TestClient(app) client.get(request_url) - (transaction_envelope,) = envelopes - transaction_event = transaction_envelope.get_transaction_event() + sentry_sdk.flush() - assert transaction_event["contexts"]["response"]["status_code"] == 400 - assert transaction_event["transaction"] == expected_transaction_name + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + assert segments[0]["name"] == expected_transaction_name assert ( - transaction_event["transaction_info"]["source"] == expected_transaction_source + segments[0]["attributes"]["sentry.segment.name.source"] + == expected_transaction_source ) -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_span_origin(sentry_init, capture_events, capture_items, span_streaming): +def test_span_origin(sentry_init, capture_items): sentry_init( auto_enabling_integrations=False, # avoid httpx auto-instrumentation leaking spans integrations=[StarletteIntegration(middleware_spans=True)], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory( middleware=[Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())] ) - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") client = TestClient(starlette_app, raise_server_exceptions=False) try: @@ -1882,35 +1740,30 @@ def test_span_origin(sentry_init, capture_events, capture_items, span_streaming) except Exception: pass - if span_streaming: - sentry_sdk.flush() - - assert len(items) > 0 - for item in items: - assert item.payload["attributes"]["sentry.origin"] == "auto.http.starlette" - else: - (_, event) = events + sentry_sdk.flush() - assert event["contexts"]["trace"]["origin"] == "auto.http.starlette" - for span in event["spans"]: - assert span["origin"] == "auto.http.starlette" + assert len(items) > 0 + for item in items: + assert item.payload["attributes"]["sentry.origin"] == "auto.http.starlette" @pytest.mark.skipif( STARLETTE_VERSION < (0, 21), reason="Requires Starlette >= 0.21, because earlier versions do not support HTTP 'HEAD' requests", ) -def test_transaction_http_method_default(sentry_init, capture_events): +def test_segment_http_method_default(sentry_init, capture_items): """ - By default OPTIONS and HEAD requests do not create a transaction. + By default OPTIONS and HEAD requests do not create a segment span. """ sentry_init( + auto_enabling_integrations=False, traces_sample_rate=1.0, integrations=[ StarletteIntegration(), ], + trace_lifecycle="stream", ) - events = capture_events() + items = capture_items("span") starlette_app = starlette_app_factory() @@ -1919,59 +1772,49 @@ def test_transaction_http_method_default(sentry_init, capture_events): client.options("/nomessage") client.head("/nomessage") - assert len(events) == 1 - - (event,) = events + sentry_sdk.flush() - assert event["request"]["method"] == "GET" + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 1 + assert segments[0]["attributes"]["http.request.method"] == "GET" -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_request_url(sentry_init, capture_events, capture_items, span_streaming): +def test_request_url(sentry_init, capture_items): sentry_init( traces_sample_rate=1.0, send_default_pii=True, integrations=[ StarletteIntegration(), ], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) starlette_app = starlette_app_factory() client = TestClient(starlette_app) - if span_streaming: - items = capture_items("span") - - client.get("/root/nomessage") - sentry_sdk.flush() - spans = [item.payload for item in items] - - (server_span,) = ( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.server" - ) - assert server_span["attributes"][SPANDATA.URL_FULL] == ( - "http://testserver/root/nomessage" - ) - assert server_span["attributes"][SPANDATA.URL_PATH] == "/root/nomessage" - else: - events = capture_events() + items = capture_items("span") - client.get("/root/nomessage") + client.get("/root/nomessage") + sentry_sdk.flush() + spans = [item.payload for item in items] - (event,) = events - assert event["request"]["url"] == "http://testserver/root/nomessage" + (server_span,) = ( + span for span in spans if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"][SPANDATA.URL_FULL] == ( + "http://testserver/root/nomessage" + ) + assert server_span["attributes"][SPANDATA.URL_PATH] == "/root/nomessage" @pytest.mark.skipif( STARLETTE_VERSION < (0, 21), reason="Requires Starlette >= 0.21, because earlier versions do not support HTTP 'HEAD' requests", ) -def test_transaction_http_method_custom(sentry_init, capture_events): +def test_segment_http_method_custom(sentry_init, capture_items): sentry_init( + auto_enabling_integrations=False, traces_sample_rate=1.0, integrations=[ StarletteIntegration( @@ -1981,9 +1824,9 @@ def test_transaction_http_method_custom(sentry_init, capture_events): ), # capitalization does not matter ), ], - debug=True, + trace_lifecycle="stream", ) - events = capture_events() + items = capture_items("span") starlette_app = starlette_app_factory() @@ -1992,12 +1835,13 @@ def test_transaction_http_method_custom(sentry_init, capture_events): client.options("/nomessage") client.head("/nomessage") - assert len(events) == 2 + sentry_sdk.flush() - (event1, event2) = events + segments = [item.payload for item in items if item.payload.get("is_segment")] + assert len(segments) == 2 - assert event1["request"]["method"] == "OPTIONS" - assert event2["request"]["method"] == "HEAD" + assert segments[0]["attributes"]["http.request.method"] == "OPTIONS" + assert segments[1]["attributes"]["http.request.method"] == "HEAD" @parametrize_test_configurable_status_codes @@ -2041,6 +1885,7 @@ async def test_malformed_json_request_body(sentry_init, capture_events): traces_sample_rate=1.0, send_default_pii=True, integrations=[StarletteIntegration()], + trace_lifecycle="stream", ) starlette_app = starlette_app_factory() @@ -2053,6 +1898,5 @@ async def test_malformed_json_request_body(sentry_init, capture_events): headers={"content-type": "application/json"}, ) - (event, transaction_event) = events + (event,) = events assert event["request"]["data"] == "" - assert transaction_event["request"]["data"] == ""