diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 96683ef05d..cbc39bd00b 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -3,8 +3,7 @@ from functools import wraps import sentry_sdk -from sentry_sdk.api import continue_trace -from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS +from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.data_collection import ( _apply_data_collection_filtering_to_query_string, ) @@ -22,7 +21,6 @@ from sentry_sdk.scope import Scope, should_send_default_pii from sentry_sdk.sessions import track_session from sentry_sdk.traces import ( - NoOpStreamedSpan, SegmentNameSource, SpanStatus, StreamedSpan, @@ -30,12 +28,10 @@ from sentry_sdk.tracing import ( BAGGAGE_HEADER_NAME, SOURCE_FOR_STYLE, - TransactionSource, ) from sentry_sdk.tracing_utils import ( add_http_breadcrumb, add_http_request_source, - has_span_streaming_enabled, should_propagate_trace, ) from sentry_sdk.utils import ( @@ -67,7 +63,7 @@ if TYPE_CHECKING: from collections.abc import Set from types import SimpleNamespace - from typing import Any, ContextManager, Optional, Tuple, Union + from typing import Any, Optional, Tuple, Union from aiohttp import TraceRequestEndParams, TraceRequestStartParams from aiohttp.web_request import Request @@ -123,7 +119,6 @@ async def sentry_app_handle( return await old_handle(self, request, *args, **kwargs) weak_request = weakref.ref(request) - is_span_streaming_enabled = has_span_streaming_enabled(client.options) with sentry_sdk.isolation_scope() as scope: with track_session(scope, session_mode="request"): @@ -135,72 +130,47 @@ async def sentry_app_handle( headers = dict(request.headers) - span_ctx: "ContextManager[Union[Span, StreamedSpan]]" - if is_span_streaming_enabled: - sentry_sdk.traces.continue_trace(headers) - Scope.set_custom_sampling_context({"aiohttp_request": request}) - - header_attributes: "dict[str, Any]" = {} - for header, header_value in _filter_headers( - headers, - use_annotated_value=False, - ).items(): - header_attributes[ - f"http.request.header.{header.lower()}" - ] = ( - # header_value will always be a string because we set `use_annotated_value` to false above - header_value - ) + sentry_sdk.traces.continue_trace(headers) + Scope.set_custom_sampling_context({"aiohttp_request": request}) + + header_attributes: "dict[str, Any]" = {} + for header, header_value in _filter_headers( + headers, + use_annotated_value=False, + ).items(): + header_attributes[f"http.request.header.{header.lower()}"] = ( + # header_value will always be a string because we set `use_annotated_value` to false above + header_value + ) - url_attributes = {} - client_address_attributes = {} + url_attributes = {} + client_address_attributes = {} - if has_data_collection_enabled(client.options): - url_attributes["url.full"] = "%s://%s%s" % ( - request.scheme, - request.host, - request.path, - ) - url_attributes["url.path"] = request.path - - if request.query_string: - filtered_query_string = ( - _apply_data_collection_filtering_to_query_string( - query_string=request.query_string, - behaviour=client.options["data_collection"][ - "url_query_params" - ], - ) + if has_data_collection_enabled(client.options): + url_attributes["url.full"] = "%s://%s%s" % ( + request.scheme, + request.host, + request.path, + ) + url_attributes["url.path"] = request.path + + if request.query_string: + filtered_query_string = ( + _apply_data_collection_filtering_to_query_string( + query_string=request.query_string, + behaviour=client.options["data_collection"][ + "url_query_params" + ], ) - if filtered_query_string: - url_attributes["url.query"] = filtered_query_string - url_attributes["url.full"] += ( - "?" + filtered_query_string - ) - - if request.remote: - if client.options["data_collection"]["user_info"]: - client_address_attributes["client.address"] = ( - request.remote - ) - scope.set_attribute( - SPANDATA.USER_IP_ADDRESS, request.remote - ) - - elif should_send_default_pii(): - url_full = "%s://%s%s" % ( - request.scheme, - request.host, - request.path, ) - if request.query_string: - url_full += "?" + request.query_string - url_attributes["url.query"] = request.query_string - - url_attributes["url.full"] = url_full - url_attributes["url.path"] = request.path + if filtered_query_string: + url_attributes["url.query"] = filtered_query_string + url_attributes["url.full"] += ( + "?" + filtered_query_string + ) - if request.remote: + if request.remote: + if client.options["data_collection"]["user_info"]: client_address_attributes["client.address"] = ( request.remote ) @@ -208,57 +178,54 @@ async def sentry_app_handle( SPANDATA.USER_IP_ADDRESS, request.remote ) - span_ctx = sentry_sdk.traces.start_span( - # If this name makes it to the UI, AIOHTTP's URL - # resolver did not find a route or died trying. - name="generic AIOHTTP request", - attributes={ - "sentry.op": OP.HTTP_SERVER, - "sentry.origin": AioHttpIntegration.origin, - "sentry.segment.name.source": SegmentNameSource.ROUTE.value, - "http.request.method": request.method, - **url_attributes, - **client_address_attributes, - **header_attributes, - }, - parent_span=None, - ) - scope.get_current_scope()._server_segment_span = span_ctx - else: - transaction = continue_trace( - headers, - op=OP.HTTP_SERVER, - # If this transaction name makes it to the UI, AIOHTTP's - # URL resolver did not find a route or died trying. - name="generic AIOHTTP request", - source=TransactionSource.ROUTE, - origin=AioHttpIntegration.origin, - ) - span_ctx = sentry_sdk.start_transaction( - transaction, - custom_sampling_context={"aiohttp_request": request}, + elif should_send_default_pii(): + url_full = "%s://%s%s" % ( + request.scheme, + request.host, + request.path, ) + if request.query_string: + url_full += "?" + request.query_string + url_attributes["url.query"] = request.query_string - with span_ctx as span: + url_attributes["url.full"] = url_full + url_attributes["url.path"] = request.path + + if request.remote: + client_address_attributes["client.address"] = request.remote + scope.set_attribute( + SPANDATA.USER_IP_ADDRESS, request.remote + ) + + span = sentry_sdk.traces.start_span( + # If this name makes it to the UI, AIOHTTP's URL + # resolver did not find a route or died trying. + name="generic AIOHTTP request", + attributes={ + "sentry.op": OP.HTTP_SERVER, + "sentry.origin": AioHttpIntegration.origin, + "sentry.segment.name.source": SegmentNameSource.ROUTE.value, + "http.request.method": request.method, + **url_attributes, + **client_address_attributes, + **header_attributes, + }, + parent_span=None, + ) + scope.get_current_scope()._server_segment_span = span + + with span: 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 - ) + 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 + if e.status_code >= 400: + span.status = SpanStatus.ERROR.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) + span.status = SpanStatus.OK.value if ( e.status_code @@ -267,10 +234,7 @@ async def sentry_app_handle( _capture_exception() raise except (asyncio.CancelledError, ConnectionResetError): - if isinstance(span, StreamedSpan): - span.status = SpanStatus.ERROR.value - else: - span.set_status(SPANSTATUS.CANCELLED) + span.status = SpanStatus.ERROR.value raise except Exception: # This will probably map to a 500 but seems like we @@ -286,17 +250,14 @@ async def sentry_app_handle( except AttributeError: pass else: - if isinstance(span, StreamedSpan): - span.set_attribute( - "http.response.status_code", response_status - ) - span.status = ( - SpanStatus.ERROR.value - if response_status >= 400 - else SpanStatus.OK.value - ) - else: - span.set_http_status(response_status) + span.set_attribute( + "http.response.status_code", response_status + ) + span.status = ( + SpanStatus.ERROR.value + if response_status >= 400 + else SpanStatus.OK.value + ) return response @@ -380,78 +341,52 @@ async def on_request_start( ) span: "Union[Span, StreamedSpan, None]" = None - if has_span_streaming_enabled(client.options): - attributes: "Attributes" = { - "sentry.op": OP.HTTP_CLIENT, - "sentry.origin": AioHttpIntegration.origin, - "http.request.method": method, - } - if parsed_url is not None: - if has_data_collection_enabled(client.options): - url_full = parsed_url.url - attributes["url.path"] = params.url.path - - if parsed_url.query: - filtered_query = ( - _apply_data_collection_filtering_to_query_string( - query_string=parsed_url.query, - behaviour=client.options["data_collection"][ - "url_query_params" - ], - ) - ) - if filtered_query: - attributes["url.query"] = filtered_query - url_full += "?" + filtered_query - breadcrumb[SPANDATA.HTTP_QUERY] = filtered_query - - if parsed_url.fragment: - attributes["url.fragment"] = parsed_url.fragment - url_full += "#" + parsed_url.fragment - breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment - - attributes["url.full"] = url_full - breadcrumb["url"] = url_full - - elif should_send_default_pii(): - url_full = parsed_url.url - attributes["url.path"] = params.url.path - - if parsed_url.query: - url_full += "?" + parsed_url.query - attributes["url.query"] = parsed_url.query - breadcrumb[SPANDATA.HTTP_QUERY] = parsed_url.query - if parsed_url.fragment: - url_full += "#" + parsed_url.fragment - attributes["url.fragment"] = parsed_url.fragment - breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment - - attributes["url.full"] = url_full - breadcrumb["url"] = url_full - - if sentry_sdk.traces.get_current_span() is not None: - span = sentry_sdk.traces.start_span( - name=span_name, attributes=attributes - ) - else: - legacy_span = sentry_sdk.start_span( - op=OP.HTTP_CLIENT, - name=span_name, - origin=AioHttpIntegration.origin, - ) - legacy_span.set_data(SPANDATA.HTTP_METHOD, method) - if parsed_url is not None: - legacy_span.set_data("url", parsed_url.url) - legacy_span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) - legacy_span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) - breadcrumb.update( - { - SPANDATA.HTTP_QUERY: parsed_url.query, - SPANDATA.HTTP_FRAGMENT: parsed_url.fragment, - "url": parsed_url.url, - } - ) - span = legacy_span + attributes: "Attributes" = { + "sentry.op": OP.HTTP_CLIENT, + "sentry.origin": AioHttpIntegration.origin, + "http.request.method": method, + } + if parsed_url is not None: + if has_data_collection_enabled(client.options): + url_full = parsed_url.url + attributes["url.path"] = params.url.path + + if parsed_url.query: + filtered_query = _apply_data_collection_filtering_to_query_string( + query_string=parsed_url.query, + behaviour=client.options["data_collection"]["url_query_params"], + ) + if filtered_query: + attributes["url.query"] = filtered_query + url_full += "?" + filtered_query + breadcrumb[SPANDATA.HTTP_QUERY] = filtered_query + + if parsed_url.fragment: + attributes["url.fragment"] = parsed_url.fragment + url_full += "#" + parsed_url.fragment + breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment + + attributes["url.full"] = url_full + breadcrumb["url"] = url_full + + elif should_send_default_pii(): + url_full = parsed_url.url + attributes["url.path"] = params.url.path + + if parsed_url.query: + url_full += "?" + parsed_url.query + attributes["url.query"] = parsed_url.query + breadcrumb[SPANDATA.HTTP_QUERY] = parsed_url.query + if parsed_url.fragment: + url_full += "#" + parsed_url.fragment + attributes["url.fragment"] = parsed_url.fragment + breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment + + attributes["url.full"] = url_full + breadcrumb["url"] = url_full + + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span(name=span_name, attributes=attributes) if should_propagate_trace(client, str(params.url)): for ( @@ -502,21 +437,12 @@ async def on_request_end( if span is None: return - if isinstance(span, StreamedSpan): - span.set_attribute("http.response.status_code", status) - span.status = ( - SpanStatus.ERROR.value if status >= 400 else SpanStatus.OK.value - ) + span.set_attribute("http.response.status_code", status) + span.status = SpanStatus.ERROR.value if status >= 400 else SpanStatus.OK.value - with capture_internal_exceptions(): - add_http_request_source(span) - span.end() - else: - span.set_http_status(status) - span.set_data("reason", params.response.reason) - span.finish() - with capture_internal_exceptions(): - add_http_request_source(span) + with capture_internal_exceptions(): + add_http_request_source(span) + span.end() trace_config = TraceConfig() diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 918148b062..16c03cb3ff 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -1,5 +1,4 @@ import asyncio -import datetime import json import os from contextlib import suppress @@ -18,12 +17,11 @@ from aiohttp.web_request import Request import sentry_sdk -from sentry_sdk import capture_message, start_transaction +from sentry_sdk import capture_message from sentry_sdk._types import OVER_SIZE_LIMIT_SUBSTITUTE from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.aiohttp import ( AioHttpIntegration, - create_trace_config, ) from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE from tests.conftest import ApproxDict @@ -298,94 +296,22 @@ async def hello(request): assert events == [] +@pytest.mark.tests_internal_exceptions @pytest.mark.asyncio -async def test_tracing(sentry_init, aiohttp_client, capture_events): - sentry_init(integrations=[AioHttpIntegration()], traces_sample_rate=1.0) - - async def hello(request): - return web.Response(text="hello") - - app = web.Application() - app.router.add_get("/", hello) - - events = capture_events() - - client = await aiohttp_client(app) - resp = await client.get("/") - assert resp.status == 200 - - (event,) = events - - assert event["type"] == "transaction" - assert ( - event["transaction"] - == "tests.integrations.aiohttp.test_aiohttp.test_tracing..hello" - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - "url,transaction_style,expected_transaction,expected_source", - [ - ( - "/message", - "handler_name", - "tests.integrations.aiohttp.test_aiohttp.test_transaction_style..hello", - "component", - ), - ( - "/message", - "method_and_path_pattern", - "GET /{var}", - "route", - ), - ], -) -async def test_transaction_style( - sentry_init, - aiohttp_client, - capture_events, - url, - transaction_style, - expected_transaction, - expected_source, -): +async def test_tracing_unparseable_url(sentry_init, aiohttp_client, capture_items): sentry_init( - integrations=[AioHttpIntegration(transaction_style=transaction_style)], + integrations=[AioHttpIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", ) - async def hello(request): - return web.Response(text="hello") - - app = web.Application() - app.router.add_get(r"/{var}", hello) - - events = capture_events() - - client = await aiohttp_client(app) - resp = await client.get(url) - assert resp.status == 200 - - (event,) = events - - assert event["type"] == "transaction" - assert event["transaction"] == expected_transaction - assert event["transaction_info"] == {"source": expected_source} - - -@pytest.mark.tests_internal_exceptions -@pytest.mark.asyncio -async def test_tracing_unparseable_url(sentry_init, aiohttp_client, capture_events): - sentry_init(integrations=[AioHttpIntegration()], traces_sample_rate=1.0) - async def hello(request): return web.Response(text="hello") app = web.Application() app.router.add_get("/", hello) - events = capture_events() + items = capture_items("span") client = await aiohttp_client(app) with mock.patch( @@ -395,11 +321,12 @@ async def hello(request): assert resp.status == 200 - (event,) = events + sentry_sdk.flush() + + (span,) = [item.payload for item in items] - assert event["type"] == "transaction" assert ( - event["transaction"] + span["name"] == "tests.integrations.aiohttp.test_aiohttp.test_tracing_unparseable_url..hello" ) @@ -414,6 +341,7 @@ async def test_traces_sampler_gets_request_object_in_sampling_context( traces_sampler = mock.Mock() sentry_init( integrations=[AioHttpIntegration()], + trace_lifecycle="stream", traces_sampler=traces_sampler, ) @@ -439,9 +367,13 @@ async def kangaroo_handler(request): @pytest.mark.asyncio async def test_has_trace_if_performance_enabled( - sentry_init, aiohttp_client, capture_events + sentry_init, aiohttp_client, capture_items ): - sentry_init(integrations=[AioHttpIntegration()], traces_sample_rate=1.0) + sentry_init( + integrations=[AioHttpIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) async def hello(request): capture_message("It's a good day to try dividing by 0") @@ -450,13 +382,29 @@ async def hello(request): app = web.Application() app.router.add_get("/", hello) - events = capture_events() + items = capture_items("event", "span") client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 500 - msg_event, error_event, transaction_event = events + sentry_sdk.flush() + + msg_events = [ + i.payload for i in items if i.type == "event" and "exception" not in i.payload + ] + error_events = [ + i.payload for i in items if i.type == "event" and "exception" in i.payload + ] + spans = [i.payload for i in items if i.type == "span"] + + assert len(msg_events) == 1 + assert len(error_events) == 1 + assert len(spans) == 1 + + (msg_event,) = msg_events + (error_event,) = error_events + (span,) = spans assert msg_event["contexts"]["trace"] assert "trace_id" in msg_event["contexts"]["trace"] @@ -464,12 +412,9 @@ async def hello(request): assert error_event["contexts"]["trace"] assert "trace_id" in error_event["contexts"]["trace"] - assert transaction_event["contexts"]["trace"] - assert "trace_id" in transaction_event["contexts"]["trace"] - assert ( error_event["contexts"]["trace"]["trace_id"] - == transaction_event["contexts"]["trace"]["trace_id"] + == span["trace_id"] == msg_event["contexts"]["trace"]["trace_id"] ) @@ -478,7 +423,7 @@ async def hello(request): async def test_has_trace_if_performance_disabled( sentry_init, aiohttp_client, capture_events ): - sentry_init(integrations=[AioHttpIntegration()]) + sentry_init(integrations=[AioHttpIntegration()], trace_lifecycle="stream") async def hello(request): capture_message("It's a good day to try dividing by 0") @@ -509,9 +454,13 @@ async def hello(request): @pytest.mark.asyncio async def test_trace_from_headers_if_performance_enabled( - sentry_init, aiohttp_client, capture_events + sentry_init, aiohttp_client, capture_items ): - sentry_init(integrations=[AioHttpIntegration()], traces_sample_rate=1.0) + sentry_init( + integrations=[AioHttpIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) async def hello(request): capture_message("It's a good day to try dividing by 0") @@ -520,22 +469,33 @@ async def hello(request): app = web.Application() app.router.add_get("/", hello) - events = capture_events() + items = capture_items("event", "span") - # The aiohttp_client is instrumented so will generate the sentry-trace header and add request. - # Get the sentry-trace header from the request so we can later compare with transaction events. client = await aiohttp_client(app) - with start_transaction(): - # Headers are only added to the span if there is an active transaction - resp = await client.get("/") + resp = await client.get("/") sentry_trace_header = resp.request_info.headers.get("sentry-trace") trace_id = sentry_trace_header.split("-")[0] assert resp.status == 500 - # Last item is the custom transaction event wrapping `client.get("/")` - msg_event, error_event, transaction_event, _ = events + sentry_sdk.flush() + + msg_events = [ + i.payload for i in items if i.type == "event" and "exception" not in i.payload + ] + error_events = [ + i.payload for i in items if i.type == "event" and "exception" in i.payload + ] + spans = [i.payload for i in items if i.type == "span"] + + assert len(msg_events) == 1 + assert len(error_events) == 1 + assert len(spans) == 1 + + (msg_event,) = msg_events + (error_event,) = error_events + (span,) = spans assert msg_event["contexts"]["trace"] assert "trace_id" in msg_event["contexts"]["trace"] @@ -543,12 +503,10 @@ async def hello(request): assert error_event["contexts"]["trace"] assert "trace_id" in error_event["contexts"]["trace"] - assert transaction_event["contexts"]["trace"] - assert "trace_id" in transaction_event["contexts"]["trace"] - assert msg_event["contexts"]["trace"]["trace_id"] == trace_id assert error_event["contexts"]["trace"]["trace_id"] == trace_id - assert transaction_event["contexts"]["trace"]["trace_id"] == trace_id + + assert span["trace_id"] == trace_id @pytest.mark.asyncio @@ -587,53 +545,6 @@ async def hello(request): assert error_event["contexts"]["trace"]["trace_id"] == trace_id -@pytest.mark.asyncio -async def test_crumb_capture( - sentry_init, - aiohttp_raw_server, - aiohttp_client, - capture_events, -): - def before_breadcrumb(crumb, hint): - crumb["data"]["extra"] = "foo" - return crumb - - sentry_init( - integrations=[AioHttpIntegration()], - before_breadcrumb=before_breadcrumb, - ) - - async def handler(request): - return web.Response(text="OK") - - raw_server = await aiohttp_raw_server(handler) - - with start_transaction(): - events = capture_events() - - client = await aiohttp_client(raw_server) - resp = await client.get("/") - assert resp.status == 200 - capture_message("Testing!") - - (event,) = events - - crumb = event["breadcrumbs"]["values"][0] - assert crumb["type"] == "http" - assert crumb["category"] == "httplib" - assert crumb["data"] == ApproxDict( - { - "url": "http://127.0.0.1:{}/".format(raw_server.port), - "http.fragment": "", - "http.method": "GET", - "http.query": "", - "http.response.status_code": 200, - "reason": "OK", - "extra": "foo", - } - ) - - @pytest.mark.asyncio @pytest.mark.parametrize( "pii_options,url_expected,query_expected", @@ -665,7 +576,7 @@ async def handler(request): ), ], ) -async def test_crumb_capture_span_streaming( +async def test_crumb_capture( sentry_init, aiohttp_raw_server, aiohttp_client, @@ -722,60 +633,6 @@ async def handler(request): assert crumb["data"] == ApproxDict(expected) -@pytest.mark.parametrize( - "status_code,level", - [ - (200, None), - (301, None), - (403, "warning"), - (405, "warning"), - (500, "error"), - ], -) -@pytest.mark.asyncio -async def test_crumb_capture_client_error( - sentry_init, - aiohttp_raw_server, - aiohttp_client, - capture_events, - status_code, - level, -): - sentry_init(integrations=[AioHttpIntegration()]) - - async def handler(request): - return web.Response(status=status_code) - - raw_server = await aiohttp_raw_server(handler) - - with start_transaction(): - events = capture_events() - - client = await aiohttp_client(raw_server) - resp = await client.get("/") - assert resp.status == status_code - capture_message("Testing!") - - (event,) = events - - crumb = event["breadcrumbs"]["values"][0] - assert crumb["type"] == "http" - if level is None: - assert "level" not in crumb - else: - assert crumb["level"] == level - assert crumb["category"] == "httplib" - assert crumb["data"] == ApproxDict( - { - "url": "http://127.0.0.1:{}/".format(raw_server.port), - "http.fragment": "", - "http.method": "GET", - "http.query": "", - "http.response.status_code": status_code, - } - ) - - @pytest.mark.parametrize( "status_code,level,reason", [ @@ -817,7 +674,7 @@ async def handler(request): ], ) @pytest.mark.asyncio -async def test_crumb_capture_client_error_span_streaming( +async def test_crumb_capture_client_error( sentry_init, aiohttp_raw_server, aiohttp_client, @@ -874,37 +731,6 @@ async def handler(request): assert crumb["data"] == ApproxDict(expected) -@pytest.mark.asyncio -async def test_outgoing_trace_headers(sentry_init, aiohttp_raw_server, aiohttp_client): - sentry_init( - integrations=[AioHttpIntegration()], - traces_sample_rate=1.0, - ) - - async def handler(request): - return web.Response(text="OK") - - raw_server = await aiohttp_raw_server(handler) - - with start_transaction( - name="/interactions/other-dogs/new-dog", - op="greeting.sniff", - # make trace_id difference between transactions - trace_id="0123456789012345678901234567890", - ) as transaction: - client = await aiohttp_client(raw_server) - resp = await client.get("/") - request_span = transaction._span_recorder.spans[-1] - - assert resp.request_info.headers[ - "sentry-trace" - ] == "{trace_id}-{parent_span_id}-{sampled}".format( - trace_id=transaction.trace_id, - parent_span_id=request_span.span_id, - sampled=1, - ) - - @pytest.mark.asyncio async def test_outgoing_trace_headers_append_to_baggage( sentry_init, aiohttp_raw_server, aiohttp_client @@ -912,6 +738,7 @@ async def test_outgoing_trace_headers_append_to_baggage( sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", release="d08ebdb9309e1b004c6f52202de58a09c2268e42", ) @@ -921,18 +748,16 @@ async def handler(request): raw_server = await aiohttp_raw_server(handler) with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000): - with start_transaction( + with sentry_sdk.traces.start_span( name="/interactions/other-dogs/new-dog", - op="greeting.sniff", - trace_id="0123456789012345678901234567890", ): client = await aiohttp_client(raw_server) resp = await client.get("/", headers={"bagGage": "custom=value"}) - assert ( - resp.request_info.headers["baggage"] - == "custom=value,sentry-trace_id=0123456789012345678901234567890,sentry-sample_rand=0.500000,sentry-environment=production,sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42,sentry-transaction=/interactions/other-dogs/new-dog,sentry-sample_rate=1.0,sentry-sampled=true" - ) + baggage = resp.request_info.headers["baggage"] + assert baggage.startswith("custom=value,") + assert "sentry-sample_rand=0.500000" in baggage + assert "sentry-sampled=true" in baggage @pytest.mark.asyncio @@ -940,16 +765,15 @@ async def test_request_source_disabled( sentry_init, aiohttp_raw_server, aiohttp_client, - capture_events, + capture_items, ): - sentry_options = { - "integrations": [AioHttpIntegration()], - "traces_sample_rate": 1.0, - "enable_http_request_source": False, - "http_request_source_threshold_ms": 0, - } - - sentry_init(**sentry_options) + sentry_init( + integrations=[AioHttpIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + enable_http_request_source=False, + http_request_source_threshold_ms=0, + ) # server for making span request async def handler(request): @@ -965,22 +789,21 @@ async def hello(request): app = web.Application() app.router.add_get(r"/", hello) - events = capture_events() + items = capture_items("span") client = await aiohttp_client(app) await client.get("/") - (event,) = events + sentry_sdk.flush() - span = event["spans"][-1] - assert span["description"].startswith("GET") + (span, segment) = [item.payload for item in items] - data = span.get("data", {}) + assert span["name"].startswith("GET") - assert SPANDATA.CODE_LINENO not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILEPATH not in data - assert SPANDATA.CODE_FUNCTION not in data + assert SPANDATA.CODE_LINENO not in span["attributes"] + assert SPANDATA.CODE_NAMESPACE not in span["attributes"] + assert SPANDATA.CODE_FILEPATH not in span["attributes"] + assert SPANDATA.CODE_FUNCTION not in span["attributes"] @pytest.mark.asyncio @@ -989,18 +812,20 @@ async def test_request_source_enabled( sentry_init, aiohttp_raw_server, aiohttp_client, - capture_events, + capture_items, enable_http_request_source, ): - sentry_options = { - "integrations": [AioHttpIntegration()], - "traces_sample_rate": 1.0, - "http_request_source_threshold_ms": 0, - } + extra_options = {} if enable_http_request_source is not None: - sentry_options["enable_http_request_source"] = enable_http_request_source + extra_options["enable_http_request_source"] = enable_http_request_source - sentry_init(**sentry_options) + sentry_init( + integrations=[AioHttpIntegration()], + traces_sample_rate=1.0, + trace_lifecycle="stream", + http_request_source_threshold_ms=0, + **extra_options, + ) # server for making span request async def handler(request): @@ -1016,31 +841,31 @@ async def hello(request): app = web.Application() app.router.add_get(r"/", hello) - events = capture_events() + items = capture_items("span") client = await aiohttp_client(app) await client.get("/") - (event,) = events + sentry_sdk.flush() - span = event["spans"][-1] - assert span["description"].startswith("GET") + (span, segment) = [item.payload for item in items] - data = span.get("data", {}) + assert span["name"].startswith("GET") - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data + assert "code.line.number" in span["attributes"] + assert "code.namespace" in span["attributes"] + assert "code.file.path" in span["attributes"] + assert "code.function" in span["attributes"] @pytest.mark.asyncio async def test_request_source( - sentry_init, aiohttp_raw_server, aiohttp_client, capture_events + sentry_init, aiohttp_raw_server, aiohttp_client, capture_items ): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", enable_http_request_source=True, http_request_source_threshold_ms=0, ) @@ -1059,41 +884,41 @@ async def handler_with_outgoing_request(request): app = web.Application() app.router.add_get(r"/", handler_with_outgoing_request) - events = capture_events() + items = capture_items("span") client = await aiohttp_client(app) await client.get("/") - (event,) = events + sentry_sdk.flush() - span = event["spans"][-1] - assert span["description"].startswith("GET") + (span, segment) = [item.payload for item in items] - data = span.get("data", {}) + assert span["name"].startswith("GET") - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data + assert "code.line.number" in span["attributes"] + assert "code.namespace" in span["attributes"] + assert "code.file.path" in span["attributes"] + assert "code.function" in span["attributes"] - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 + assert type(span["attributes"]["code.line.number"]) == int + assert span["attributes"]["code.line.number"] > 0 assert ( - data.get(SPANDATA.CODE_NAMESPACE) == "tests.integrations.aiohttp.test_aiohttp" + span["attributes"]["code.namespace"] + == "tests.integrations.aiohttp.test_aiohttp" ) - assert data.get(SPANDATA.CODE_FILEPATH).endswith( + assert span["attributes"]["code.file.path"].endswith( "tests/integrations/aiohttp/test_aiohttp.py" ) - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep + is_relative_path = span["attributes"]["code.file.path"][0] != os.sep assert is_relative_path - assert data.get(SPANDATA.CODE_FUNCTION) == "handler_with_outgoing_request" + assert span["attributes"]["code.function"] == "handler_with_outgoing_request" @pytest.mark.asyncio async def test_request_source_with_module_in_search_path( - sentry_init, aiohttp_raw_server, aiohttp_client, capture_events + sentry_init, aiohttp_raw_server, aiohttp_client, capture_items ): """ Test that request source is relative to the path of the module it ran in @@ -1101,6 +926,7 @@ async def test_request_source_with_module_in_search_path( sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", enable_http_request_source=True, http_request_source_threshold_ms=0, ) @@ -1121,43 +947,43 @@ async def handler_with_outgoing_request(request): app = web.Application() app.router.add_get(r"/", handler_with_outgoing_request) - events = capture_events() + items = capture_items("span") client = await aiohttp_client(app) await client.get("/") - (event,) = events + sentry_sdk.flush() - span = event["spans"][-1] - assert span["description"].startswith("GET") + (span, segment) = [item.payload for item in items] - data = span.get("data", {}) + assert span["name"].startswith("GET") - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data + assert "code.line.number" in span["attributes"] + assert "code.namespace" in span["attributes"] + assert "code.file.path" in span["attributes"] + assert "code.function" in span["attributes"] - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 - assert data.get(SPANDATA.CODE_NAMESPACE) == "aiohttp_helpers.helpers" - assert data.get(SPANDATA.CODE_FILEPATH) == "aiohttp_helpers/helpers.py" + assert type(span["attributes"]["code.line.number"]) == int + assert span["attributes"]["code.line.number"] > 0 + assert span["attributes"]["code.namespace"] == "aiohttp_helpers.helpers" + assert span["attributes"]["code.file.path"] == "aiohttp_helpers/helpers.py" - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep + is_relative_path = span["attributes"]["code.file.path"][0] != os.sep assert is_relative_path - assert data.get(SPANDATA.CODE_FUNCTION) == "get_request_with_client" + assert span["attributes"]["code.function"] == "get_request_with_client" @pytest.mark.asyncio async def test_no_request_source_if_duration_too_short( - sentry_init, aiohttp_raw_server, aiohttp_client, capture_events + sentry_init, aiohttp_raw_server, aiohttp_client, capture_items ): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", enable_http_request_source=True, - http_request_source_threshold_ms=100, + http_request_source_threshold_ms=10**10, ) # server for making span request @@ -1174,49 +1000,36 @@ async def handler_with_outgoing_request(request): app = web.Application() app.router.add_get(r"/", handler_with_outgoing_request) - events = capture_events() - - def fake_create_trace_context(*args, **kwargs): - trace_context = create_trace_config() - - async def overwrite_timestamps(session, trace_config_ctx, params): - span = trace_config_ctx._sentry_span - span.start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) - span.timestamp = datetime.datetime(2024, 1, 1, microsecond=99999) - - trace_context.on_request_end.insert(0, overwrite_timestamps) - - return trace_context + items = capture_items("span") - with mock.patch( - "sentry_sdk.integrations.aiohttp.create_trace_config", - fake_create_trace_context, - ): - client = await aiohttp_client(app) - await client.get("/") + client = await aiohttp_client(app) + await client.get("/") - (event,) = events + sentry_sdk.flush() - span = event["spans"][-1] - assert span["description"].startswith("GET") + ( + span, + segment, + ) = [item.payload for item in items] - data = span.get("data", {}) + assert span["name"].startswith("GET") - assert SPANDATA.CODE_LINENO not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILEPATH not in data - assert SPANDATA.CODE_FUNCTION not in data + assert SPANDATA.CODE_LINENO not in span["attributes"] + assert SPANDATA.CODE_NAMESPACE not in span["attributes"] + assert SPANDATA.CODE_FILEPATH not in span["attributes"] + assert SPANDATA.CODE_FUNCTION not in span["attributes"] @pytest.mark.asyncio async def test_request_source_if_duration_over_threshold( - sentry_init, aiohttp_raw_server, aiohttp_client, capture_events + sentry_init, aiohttp_raw_server, aiohttp_client, capture_items ): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, + trace_lifecycle="stream", enable_http_request_source=True, - http_request_source_threshold_ms=100, + http_request_source_threshold_ms=0, ) # server for making span request @@ -1233,52 +1046,36 @@ async def handler_with_outgoing_request(request): app = web.Application() app.router.add_get(r"/", handler_with_outgoing_request) - events = capture_events() - - def fake_create_trace_context(*args, **kwargs): - trace_context = create_trace_config() - - async def overwrite_timestamps(session, trace_config_ctx, params): - span = trace_config_ctx._sentry_span - span.start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) - span.timestamp = datetime.datetime(2024, 1, 1, microsecond=100001) - - trace_context.on_request_end.insert(0, overwrite_timestamps) - - return trace_context + items = capture_items("span") - with mock.patch( - "sentry_sdk.integrations.aiohttp.create_trace_config", - fake_create_trace_context, - ): - client = await aiohttp_client(app) - await client.get("/") + client = await aiohttp_client(app) + await client.get("/") - (event,) = events + sentry_sdk.flush() - span = event["spans"][-1] - assert span["description"].startswith("GET") + (span, segment) = [item.payload for item in items] - data = span.get("data", {}) + assert span["name"].startswith("GET") - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data - assert SPANDATA.CODE_FUNCTION in data + assert "code.line.number" in span["attributes"] + assert "code.namespace" in span["attributes"] + assert "code.file.path" in span["attributes"] + assert "code.function" in span["attributes"] - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 + assert type(span["attributes"]["code.line.number"]) == int + assert span["attributes"]["code.line.number"] > 0 assert ( - data.get(SPANDATA.CODE_NAMESPACE) == "tests.integrations.aiohttp.test_aiohttp" + span["attributes"]["code.namespace"] + == "tests.integrations.aiohttp.test_aiohttp" ) - assert data.get(SPANDATA.CODE_FILEPATH).endswith( + assert span["attributes"]["code.file.path"].endswith( "tests/integrations/aiohttp/test_aiohttp.py" ) - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep + is_relative_path = span["attributes"]["code.file.path"][0] != os.sep assert is_relative_path - assert data.get(SPANDATA.CODE_FUNCTION) == "handler_with_outgoing_request" + assert span["attributes"]["code.function"] == "handler_with_outgoing_request" @pytest.mark.asyncio @@ -1286,10 +1083,11 @@ async def test_span_origin( sentry_init, aiohttp_raw_server, aiohttp_client, - capture_events, + capture_items, ): sentry_init( integrations=[AioHttpIntegration()], + trace_lifecycle="stream", traces_sample_rate=1.0, ) @@ -1307,14 +1105,16 @@ async def hello(request): app = web.Application() app.router.add_get(r"/", hello) - events = capture_events() + items = capture_items("span") client = await aiohttp_client(app) await client.get("/") - (event,) = events - assert event["contexts"]["trace"]["origin"] == "auto.http.aiohttp" - assert event["spans"][0]["origin"] == "auto.http.aiohttp" + sentry_sdk.flush() + + (span, segment) = [item.payload for item in items] + assert span["attributes"]["sentry.origin"] == "auto.http.aiohttp" + assert segment["attributes"]["sentry.origin"] == "auto.http.aiohttp" @pytest.mark.parametrize( @@ -1434,9 +1234,7 @@ async def handle(_): @pytest.mark.asyncio @pytest.mark.parametrize("send_pii", [True, False]) -async def test_tracing_span_streaming( - sentry_init, aiohttp_client, capture_items, send_pii -): +async def test_tracing(sentry_init, aiohttp_client, capture_items, send_pii): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, @@ -1468,7 +1266,7 @@ async def hello(request): assert server_span["is_segment"] is True assert ( server_span["name"] - == "tests.integrations.aiohttp.test_aiohttp.test_tracing_span_streaming..hello" + == "tests.integrations.aiohttp.test_aiohttp.test_tracing..hello" ) assert server_span["attributes"]["sentry.op"] == "http.server" assert server_span["attributes"]["sentry.origin"] == "auto.http.aiohttp" @@ -1506,7 +1304,7 @@ async def hello(request): @pytest.mark.asyncio @pytest.mark.parametrize("init_kwargs, expect_ip", DATA_COLLECTION_USER_INFO_CASES) -async def test_user_address_with_data_collection_and_span_streaming( +async def test_user_address_with_data_collection( sentry_init, aiohttp_client, capture_items, init_kwargs, expect_ip ): sentry_init( @@ -1542,9 +1340,7 @@ async def hello(request): @pytest.mark.asyncio -async def test_sensitive_header_scrubbing_span_streaming( - sentry_init, aiohttp_client, capture_items -): +async def test_sensitive_header_scrubbing(sentry_init, aiohttp_client, capture_items): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, @@ -1701,7 +1497,7 @@ async def hello(request): ], ) @pytest.mark.asyncio -async def test_sensitive_header_passthrough_with_pii_span_streaming( +async def test_sensitive_header_passthrough_with_pii( sentry_init, aiohttp_client, capture_items, options, expected, request ): sentry_init( @@ -1755,7 +1551,7 @@ async def hello(request): @pytest.mark.asyncio -async def test_sensitive_header_passthrough_with_pii_span_streaming_without_data_collection( +async def test_sensitive_header_passthrough_with_pii_without_data_collection( sentry_init, aiohttp_client, capture_items ): sentry_init( @@ -1793,7 +1589,7 @@ async def hello(request): @pytest.mark.asyncio @pytest.mark.parametrize("send_pii", [True, False]) -async def test_url_query_attribute_span_streaming( +async def test_url_query_attribute( sentry_init, aiohttp_client, capture_items, send_pii ): sentry_init( @@ -1834,7 +1630,7 @@ async def hello(request): "/message", "handler_name", "tests.integrations.aiohttp.test_aiohttp." - "test_transaction_style_span_streaming..hello", + "test_transaction_style..hello", "component", ), ( @@ -1845,7 +1641,7 @@ async def hello(request): ), ], ) -async def test_transaction_style_span_streaming( +async def test_transaction_style( sentry_init, aiohttp_client, capture_items, @@ -1919,7 +1715,7 @@ async def hello(request): @pytest.mark.asyncio -async def test_server_error_span_streaming(sentry_init, aiohttp_client, capture_items): +async def test_server_error(sentry_init, aiohttp_client, capture_items): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, @@ -1958,9 +1754,7 @@ async def hello(request): @pytest.mark.asyncio -async def test_http_exception_span_streaming( - sentry_init, aiohttp_client, capture_items -): +async def test_http_exception(sentry_init, aiohttp_client, capture_items): sentry_init( integrations=[AioHttpIntegration()], traces_sample_rate=1.0, @@ -1990,7 +1784,7 @@ async def hello(request): @pytest.mark.asyncio -async def test_http_exception_ok_status_not_overridden_span_streaming( +async def test_http_exception_ok_status_not_overridden( sentry_init, aiohttp_client, capture_items ): sentry_init( @@ -2023,7 +1817,7 @@ async def hello(request): @pytest.mark.asyncio @pytest.mark.parametrize("send_pii", [True, False]) -async def test_outgoing_client_span_span_streaming( +async def test_outgoing_client_span( sentry_init, aiohttp_raw_server, aiohttp_client, capture_items, send_pii ): sentry_init( @@ -2083,7 +1877,7 @@ async def hello(request): @pytest.mark.asyncio -async def test_outgoing_trace_headers_span_streaming( +async def test_outgoing_trace_headers( sentry_init, aiohttp_raw_server, aiohttp_client, capture_items ): sentry_init( @@ -2228,7 +2022,7 @@ async def hello(request): @pytest.mark.parametrize( "init_kwargs, expected_query", _QUERY_PARAM_DATA_COLLECTION_CASES ) -async def test_server_url_query_data_collection_span_streaming( +async def test_server_url_query_data_collection( sentry_init, aiohttp_client, capture_items, init_kwargs, expected_query ): init_kwargs = dict(init_kwargs) @@ -2265,7 +2059,7 @@ async def hello(request): @pytest.mark.parametrize( "init_kwargs, expected_query", _QUERY_PARAM_DATA_COLLECTION_CASES ) -async def test_client_url_query_data_collection_span_streaming( +async def test_client_url_query_data_collection( sentry_init, aiohttp_raw_server, aiohttp_client,