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: