From 337d8cd9e686547ac6d974ffe73cd75a0f85ddec Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Tue, 19 May 2026 10:12:47 -0400 Subject: [PATCH] feat(aiohttp): Remove request body capture from span streaming Remove logic that captured request body data on span segments in the aiohttp integration when span streaming is enabled. This simplifies the request handling flow and removes the need for post-handler body data capture. Also removes the following test cases that validated this behavior: - test_request_body_captured_on_segment_span_streaming - test_request_body_not_read_span_streaming - test_request_body_over_size_limit_span_streaming Remove unused imports from test file. Fixes PY-2422 Fixes #6292 --- sentry_sdk/integrations/aiohttp.py | 83 +++++++---------- tests/integrations/aiohttp/test_aiohttp.py | 103 --------------------- 2 files changed, 32 insertions(+), 154 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 3d1ee98f08..3282918490 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -201,60 +201,41 @@ async def sentry_app_handle( with span_ctx as span: try: - try: - response = await old_handle(self, request) - except HTTPException as e: - if isinstance(span, StreamedSpan) and not isinstance( - span, NoOpStreamedSpan - ): - span.set_attribute( - "http.response.status_code", e.status_code - ) - - if e.status_code >= 400: - span.status = SpanStatus.ERROR.value - else: - span.status = SpanStatus.OK.value - else: - # Since a NoOpStreamedSpan can end up here, we have to guard against it - # so this only gets set in the legacy transaction approach. - if not isinstance(span, NoOpStreamedSpan): - span.set_http_status(e.status_code) - - if ( - e.status_code - in integration._failed_request_status_codes - ): - _capture_exception() - raise - except (asyncio.CancelledError, ConnectionResetError): - if isinstance(span, StreamedSpan): - span.status = SpanStatus.ERROR.value - else: - span.set_status(SPANSTATUS.CANCELLED) - raise - except Exception: - # This will probably map to a 500 but seems like we - # have no way to tell. Do not set span status. - reraise(*_capture_exception()) - finally: - # The handler has had a chance to read the body, so - # request._read_bytes may now be populated. Capture - # body data on the segment regardless of outcome. + response = await old_handle(self, request) + except HTTPException as e: if isinstance(span, StreamedSpan) and not isinstance( span, NoOpStreamedSpan ): - with capture_internal_exceptions(): - raw_data = get_aiohttp_request_data(request) - body_data = ( - raw_data.value - if isinstance(raw_data, AnnotatedValue) - else raw_data - ) - if body_data is not None: - span._segment.set_attribute( - "http.request.body.data", body_data - ) + span.set_attribute( + "http.response.status_code", e.status_code + ) + + if e.status_code >= 400: + span.status = SpanStatus.ERROR.value + else: + span.status = SpanStatus.OK.value + else: + # Since a NoOpStreamedSpan can end up here, we have to guard against it + # so this only gets set in the legacy transaction approach. + if not isinstance(span, NoOpStreamedSpan): + span.set_http_status(e.status_code) + + if ( + e.status_code + in integration._failed_request_status_codes + ): + _capture_exception() + raise + except (asyncio.CancelledError, ConnectionResetError): + if isinstance(span, StreamedSpan): + span.status = SpanStatus.ERROR.value + else: + span.set_status(SPANSTATUS.CANCELLED) + raise + except Exception: + # This will probably map to a 500 but seems like we + # have no way to tell. Do not set span status. + reraise(*_capture_exception()) try: # A valid response handler will return a valid response with a status. But, if the handler diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index de2d3a9998..0a0032c1cf 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -19,10 +19,8 @@ import sentry_sdk from sentry_sdk import capture_message, start_transaction -from sentry_sdk._types import OVER_SIZE_LIMIT_SUBSTITUTE from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.aiohttp import ( - BODY_NOT_READ_MESSAGE, AioHttpIntegration, create_trace_config, ) @@ -1283,107 +1281,6 @@ async def hello(request): assert server_span["attributes"]["user.ip_address"] == "127.0.0.1" -@pytest.mark.asyncio -async def test_request_body_captured_on_segment_span_streaming( - sentry_init, aiohttp_client, capture_items -): - sentry_init( - integrations=[AioHttpIntegration()], - traces_sample_rate=1.0, - _experiments={"trace_lifecycle": "stream"}, - ) - - body = {"some": "value"} - - async def hello(request): - # Reading the body populates request._read_bytes; the integration - # captures body data in a finally after the handler returns. - await request.json() - return web.Response(text="hello") - - app = web.Application() - app.router.add_post("/", hello) - - items = capture_items("span") - - client = await aiohttp_client(app) - resp = await client.post("/", json=body) - assert resp.status == 200 - - sentry_sdk.flush() - - server_segment, client_segment = [item.payload for item in items] - assert server_segment["is_segment"] is True - assert server_segment["attributes"]["http.request.body.data"] == json.dumps(body) - - -@pytest.mark.asyncio -async def test_request_body_not_read_span_streaming( - sentry_init, aiohttp_client, capture_items -): - sentry_init( - integrations=[AioHttpIntegration()], - traces_sample_rate=1.0, - _experiments={"trace_lifecycle": "stream"}, - ) - - async def hello(request): - # Handler does not read the body; request._read_bytes stays None. - return web.Response(text="hello") - - app = web.Application() - app.router.add_post("/", hello) - - items = capture_items("span") - - client = await aiohttp_client(app) - resp = await client.post("/", json={"some": "value"}) - assert resp.status == 200 - - sentry_sdk.flush() - - server_segment, client_segment = [item.payload for item in items] - assert server_segment["is_segment"] is True - assert ( - server_segment["attributes"]["http.request.body.data"] == BODY_NOT_READ_MESSAGE - ) - - -@pytest.mark.asyncio -async def test_request_body_over_size_limit_span_streaming( - sentry_init, aiohttp_client, capture_items -): - sentry_init( - integrations=[AioHttpIntegration()], - traces_sample_rate=1.0, - max_request_body_size="small", - _experiments={"trace_lifecycle": "stream"}, - ) - - async def hello(request): - await request.read() - return web.Response(text="hello") - - app = web.Application() - app.router.add_post("/", hello) - - items = capture_items("span") - - client = await aiohttp_client(app) - # "small" caps at 1 KB; send a body larger than that. - resp = await client.post("/", data=b"x" * 2000) - assert resp.status == 200 - - sentry_sdk.flush() - - server_segment, client_segment = [item.payload for item in items] - assert server_segment["is_segment"] is True - assert ( - server_segment["attributes"]["http.request.body.data"] - == OVER_SIZE_LIMIT_SUBSTITUTE - ) - - @pytest.mark.asyncio async def test_url_query_attribute_span_streaming( sentry_init, aiohttp_client, capture_items