From 33b121ab71babbcf685af6a47f66b835bf4fd47d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 31 Aug 2026 13:02:39 +0200 Subject: [PATCH] chore(boto3): Remove transaction-based tracing --- sentry_sdk/integrations/boto3.py | 124 ++---- .../boto3/test_aws_http_connection.py | 111 ++--- tests/integrations/boto3/test_s3.py | 421 ++++++------------ .../boto3/test_trace_propagation.py | 57 +-- 4 files changed, 224 insertions(+), 489 deletions(-) diff --git a/sentry_sdk/integrations/boto3.py b/sentry_sdk/integrations/boto3.py index a8d927a4b3..107e8b3dfd 100644 --- a/sentry_sdk/integrations/boto3.py +++ b/sentry_sdk/integrations/boto3.py @@ -6,11 +6,10 @@ from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan -from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, Span +from sentry_sdk.tracing import BAGGAGE_HEADER_NAME from sentry_sdk.tracing_utils import ( add_http_breadcrumb, add_sentry_baggage_to_headers, - has_span_streaming_enabled, should_propagate_trace, ) from sentry_sdk.utils import ( @@ -20,7 +19,7 @@ ) if TYPE_CHECKING: - from typing import Any, Dict, Optional, Type, Union + from typing import Any, Dict, Optional, Type from botocore.model import ServiceId @@ -78,11 +77,30 @@ def _sentry_request_created( breadcrumb: "dict[str, Any]" = {} - is_span_streaming_enabled = has_span_streaming_enabled(client.options) - span: "Union[Span, StreamedSpan, None]" = None - if is_span_streaming_enabled: + span: "Optional[StreamedSpan]" = None + if parsed_url and should_send_default_pii(): + breadcrumb.update( + { + SPANDATA.URL_FULL: parsed_url.url, + SPANDATA.URL_QUERY: parsed_url.query, + SPANDATA.URL_FRAGMENT: parsed_url.fragment, + } + ) + + if request.method is not None: + breadcrumb[SPANDATA.HTTP_REQUEST_METHOD] = request.method + + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=description, + attributes={ + "sentry.op": OP.HTTP_CLIENT, + "sentry.origin": Boto3Integration.origin, + SPANDATA.RPC_METHOD: f"{service_id}/{operation_name}", + }, + ) if parsed_url and should_send_default_pii(): - breadcrumb.update( + span.set_attributes( { SPANDATA.URL_FULL: parsed_url.url, SPANDATA.URL_QUERY: parsed_url.query, @@ -91,54 +109,7 @@ def _sentry_request_created( ) if request.method is not None: - breadcrumb[SPANDATA.HTTP_REQUEST_METHOD] = request.method - - if sentry_sdk.traces.get_current_span() is not None: - span = sentry_sdk.traces.start_span( - name=description, - attributes={ - "sentry.op": OP.HTTP_CLIENT, - "sentry.origin": Boto3Integration.origin, - SPANDATA.RPC_METHOD: f"{service_id}/{operation_name}", - }, - ) - if parsed_url and should_send_default_pii(): - span.set_attributes( - { - SPANDATA.URL_FULL: parsed_url.url, - SPANDATA.URL_QUERY: parsed_url.query, - SPANDATA.URL_FRAGMENT: parsed_url.fragment, - } - ) - - if request.method is not None: - span.set_attribute(SPANDATA.HTTP_REQUEST_METHOD, request.method) - else: - span = sentry_sdk.start_span( - op=OP.HTTP_CLIENT, - name=description, - origin=Boto3Integration.origin, - ) - - if parsed_url: - span.set_data("aws.request.url", parsed_url.url) - span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) - span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) - breadcrumb.update( - { - "aws.request.url": parsed_url.url, - SPANDATA.HTTP_QUERY: parsed_url.query, - SPANDATA.HTTP_FRAGMENT: parsed_url.fragment, - } - ) - - if request.method is not None: - span.set_data(SPANDATA.HTTP_METHOD, request.method) - breadcrumb[SPANDATA.HTTP_METHOD] = request.method - - # We do it in order for subsequent http calls/retries be - # attached to this span. - span.__enter__() + span.set_attribute(SPANDATA.HTTP_REQUEST_METHOD, request.method) add_http_breadcrumb(None, breadcrumb) @@ -205,7 +176,7 @@ def _replace_header(request: "AWSRequest", key: str, value: str) -> None: def _sentry_after_call( context: "Dict[str, Any]", parsed: "Dict[str, Any]", **kwargs: "Any" ) -> None: - span: "Optional[Union[Span, StreamedSpan]]" = context.pop("_sentrysdk_span", None) + span: "Optional[StreamedSpan]" = context.pop("_sentrysdk_span", None) # Span could be absent if the integration is disabled. if span is None: @@ -217,22 +188,14 @@ def _sentry_after_call( if not isinstance(body, StreamingBody): return - streaming_span: "Union[Span, StreamedSpan]" - if isinstance(span, StreamedSpan): - streaming_span = sentry_sdk.traces.start_span( - name=span.name, - parent_span=span, - attributes={ - "sentry.op": OP.HTTP_CLIENT_STREAM, - "sentry.origin": Boto3Integration.origin, - }, - ) - else: - streaming_span = span.start_child( - op=OP.HTTP_CLIENT_STREAM, - name=span.description, - origin=Boto3Integration.origin, - ) + streaming_span = sentry_sdk.traces.start_span( + name=span.name, + parent_span=span, + attributes={ + "sentry.op": OP.HTTP_CLIENT_STREAM, + "sentry.origin": Boto3Integration.origin, + }, + ) orig_read = body.read orig_close = body.close @@ -243,25 +206,16 @@ def sentry_streaming_body_read(*args: "Any", **kwargs: "Any") -> bytes: if ret: return ret - if isinstance(streaming_span, StreamedSpan): - streaming_span.end() - else: - streaming_span.finish() + streaming_span.end() return ret except Exception: - if isinstance(streaming_span, StreamedSpan): - streaming_span.end() - else: - streaming_span.finish() + streaming_span.end() raise body.read = sentry_streaming_body_read # type: ignore def sentry_streaming_body_close(*args: "Any", **kwargs: "Any") -> None: - if isinstance(streaming_span, StreamedSpan): - streaming_span.end() - else: - streaming_span.finish() + streaming_span.end() orig_close(*args, **kwargs) body.close = sentry_streaming_body_close # type: ignore @@ -270,7 +224,7 @@ def sentry_streaming_body_close(*args: "Any", **kwargs: "Any") -> None: def _sentry_after_call_error( context: "Dict[str, Any]", exception: "Type[BaseException]", **kwargs: "Any" ) -> None: - span: "Optional[Union[Span, StreamedSpan]]" = context.pop("_sentrysdk_span", None) + span: "Optional[StreamedSpan]" = context.pop("_sentrysdk_span", None) # Span could be absent if the integration is disabled. if span is None: diff --git a/tests/integrations/boto3/test_aws_http_connection.py b/tests/integrations/boto3/test_aws_http_connection.py index 684f89d385..cce6b53aaf 100644 --- a/tests/integrations/boto3/test_aws_http_connection.py +++ b/tests/integrations/boto3/test_aws_http_connection.py @@ -45,25 +45,20 @@ def _request(server, headers, path="/"): connection.close() -@pytest.mark.parametrize("span_streaming", [False, True]) def test_aws_http_connection_adds_missing_unsigned_propagation_headers( - sentry_init, local_http_server, span_streaming + sentry_init, + local_http_server, ): """Add missing unsigned `sentry-trace` and `baggage`.""" sentry_init( traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", default_integrations=False, integrations=[StdlibIntegration()], ) server, requests = local_http_server - - if span_streaming: - with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] - _request(server, []) - else: - with sentry_sdk.start_transaction(name="test", sampled=True): - _request(server, []) + with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] + _request(server, []) headers: HTTPMessage = requests[0] @@ -78,37 +73,26 @@ def test_aws_http_connection_adds_missing_unsigned_propagation_headers( assert len(sentry_trace_headers) == 1 -@pytest.mark.parametrize("span_streaming", [False, True]) def test_aws_http_connection_appends_baggage_but_preserves_sentry_trace( - sentry_init, local_http_server, span_streaming + sentry_init, + local_http_server, ): """Append unsigned `baggage`; leave existing `sentry-trace` as-is.""" sentry_init( traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", default_integrations=False, integrations=[StdlibIntegration()], ) server, requests = local_http_server - - if span_streaming: - with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] - _request( - server, - [ - ("baggage", "vendor=value"), - ("sentry-trace", "existing-trace"), - ], - ) - else: - with sentry_sdk.start_transaction(name="test", sampled=True): - _request( - server, - [ - ("baggage", "vendor=value"), - ("sentry-trace", "existing-trace"), - ], - ) + with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] + _request( + server, + [ + ("baggage", "vendor=value"), + ("sentry-trace", "existing-trace"), + ], + ) headers: HTTPMessage = requests[0] @@ -123,14 +107,14 @@ def test_aws_http_connection_appends_baggage_but_preserves_sentry_trace( assert headers.get_all("sentry-trace") == ["existing-trace"] -@pytest.mark.parametrize("span_streaming", [False, True]) def test_aws_http_connection_preserves_signed_propagation_headers( - sentry_init, local_http_server, span_streaming + sentry_init, + local_http_server, ): """Leave signed `sentry-trace` and `baggage` as-is.""" sentry_init( traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", default_integrations=False, integrations=[StdlibIntegration()], ) @@ -143,27 +127,15 @@ def test_aws_http_connection_preserves_signed_propagation_headers( "SignedHeaders=baggage;host;sentry-trace, " "Signature=sixtyseven" ) - - if span_streaming: - with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] - _request( - server, - [ - ("baggage", "vendor=value"), - ("sentry-trace", "existing-trace"), - ("Authorization", authorization), - ], - ) - else: - with sentry_sdk.start_transaction(name="test", sampled=True): - _request( - server, - [ - ("baggage", "vendor=value"), - ("sentry-trace", "existing-trace"), - ("Authorization", authorization), - ], - ) + with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] + _request( + server, + [ + ("baggage", "vendor=value"), + ("sentry-trace", "existing-trace"), + ("Authorization", authorization), + ], + ) headers: HTTPMessage = requests[0] @@ -180,14 +152,14 @@ def test_aws_http_connection_preserves_signed_propagation_headers( } -@pytest.mark.parametrize("span_streaming", [False, True]) def test_aws_http_connection_preserves_query_signed_baggage( - sentry_init, local_http_server, span_streaming + sentry_init, + local_http_server, ): """Leave query-signed `baggage` as-is; add unsigned `sentry-trace`.""" sentry_init( traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", default_integrations=False, integrations=[StdlibIntegration()], ) @@ -202,21 +174,12 @@ def test_aws_http_connection_preserves_query_signed_baggage( "&X-Amz-SignedHeaders=baggage%3Bhost" "&X-Amz-Signature=sixtyseven" ) - - if span_streaming: - with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] - _request( - server, - [("baggage", "vendor=value")], - path=path, - ) - else: - with sentry_sdk.start_transaction(name="test", sampled=True): - _request( - server, - [("baggage", "vendor=value")], - path=path, - ) + with sentry_sdk.traces.start_span(name="test"): # type: ignore[attr-defined] + _request( + server, + [("baggage", "vendor=value")], + path=path, + ) headers: HTTPMessage = requests[0] # query-signed `baggage`: leave as-is. diff --git a/tests/integrations/boto3/test_s3.py b/tests/integrations/boto3/test_s3.py index aeab5f58a1..957a4d7e12 100644 --- a/tests/integrations/boto3/test_s3.py +++ b/tests/integrations/boto3/test_s3.py @@ -17,12 +17,9 @@ ) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_basic( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( traces_sample_rate=1.0, @@ -30,372 +27,210 @@ def test_basic( # disabled because session.resource() or s3.Bucket() result in a subprocess span for a # shell that runs "uname -p 2> /dev/null" on Python 3.7 with boto3 version 1.12.49. default_integrations=False, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) s3 = session.resource("s3") bucket = s3.Bucket("bucket") + items = capture_items("span") - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( - s3.meta.client, 200, {}, read_fixture("s3_list.xml") - ): - objects = [obj for obj in bucket.objects.all()] - assert len(objects) == 2 - assert objects[0].key == "foo.txt" - assert objects[1].key == "bar.txt" - span.end() - - sentry_sdk.flush() - spans = [item.payload for item in items] - assert len(spans) == 2 - span = spans[0] - assert span["attributes"]["sentry.op"] == "http.client" - assert span["name"] == "aws.s3.ListObjects" - else: - events = capture_events() - - with sentry_sdk.start_transaction() as transaction, MockResponse( - s3.meta.client, 200, {}, read_fixture("s3_list.xml") - ): - items = [obj for obj in bucket.objects.all()] - assert len(items) == 2 - assert items[0].key == "foo.txt" - assert items[1].key == "bar.txt" - transaction.finish() + with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( + s3.meta.client, 200, {}, read_fixture("s3_list.xml") + ): + objects = [obj for obj in bucket.objects.all()] + assert len(objects) == 2 + assert objects[0].key == "foo.txt" + assert objects[1].key == "bar.txt" + span.end() - (event,) = events - assert event["type"] == "transaction" - assert len(event["spans"]) == 1 - (span,) = event["spans"] - assert span["op"] == "http.client" - assert span["description"] == "aws.s3.ListObjects" + sentry_sdk.flush() + spans = [item.payload for item in items] + assert len(spans) == 2 + span = spans[0] + assert span["attributes"]["sentry.op"] == "http.client" + assert span["name"] == "aws.s3.ListObjects" @pytest.mark.parametrize("send_default_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_streaming( sentry_init, - capture_events, capture_items, - span_streaming, send_default_pii, ): sentry_init( traces_sample_rate=1.0, integrations=[Boto3Integration()], send_default_pii=send_default_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) s3 = session.resource("s3") obj = s3.Bucket("bucket").Object("foo.pdf") + items = capture_items("span") - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( - s3.meta.client, 200, {}, b"hello" - ): - body = obj.get()["Body"] - assert body.read(1) == b"h" - assert body.read(2) == b"el" - assert body.read(3) == b"lo" - assert body.read(1) == b"" - span.end() - - sentry_sdk.flush() - spans = [item.payload for item in items] - assert len(spans) == 3 - - span1 = spans[0] - assert span1["attributes"]["sentry.op"] == "http.client" - assert span1["name"] == "aws.s3.GetObject" - - expected_attrs = { - "http.request.method": "GET", - "rpc.method": "S3/GetObject", - "sentry.environment": "production", - "sentry.op": "http.client", - "sentry.origin": "auto.http.boto3", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, - "sentry.segment.name": "custom parent", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, - } - if send_default_pii: - expected_attrs["url.full"] = "https://bucket.s3.amazonaws.com/foo.pdf" - expected_attrs["url.fragment"] = "" - expected_attrs["url.query"] = "" - assert span1["attributes"] == ApproxDict(expected_attrs) - - if not send_default_pii: - assert "url.full" not in span1["attributes"] - assert "url.fragment" not in span1["attributes"] - assert "url.query" not in span1["attributes"] - - span2 = spans[1] - assert span2["attributes"]["sentry.op"] == "http.client.stream" - assert span2["name"] == "aws.s3.GetObject" - assert span2["parent_span_id"] == span1["span_id"] - else: - events = capture_events() + with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( + s3.meta.client, 200, {}, b"hello" + ): + body = obj.get()["Body"] + assert body.read(1) == b"h" + assert body.read(2) == b"el" + assert body.read(3) == b"lo" + assert body.read(1) == b"" + span.end() + + sentry_sdk.flush() + spans = [item.payload for item in items] + assert len(spans) == 3 + + span1 = spans[0] + assert span1["attributes"]["sentry.op"] == "http.client" + assert span1["name"] == "aws.s3.GetObject" + + expected_attrs = { + "http.request.method": "GET", + "rpc.method": "S3/GetObject", + "sentry.environment": "production", + "sentry.op": "http.client", + "sentry.origin": "auto.http.boto3", + "sentry.release": mock.ANY, + "sentry.sdk.name": "sentry.python", + "sentry.sdk.version": mock.ANY, + "sentry.segment.id": mock.ANY, + "sentry.segment.name": "custom parent", + "server.address": mock.ANY, + "thread.id": mock.ANY, + "thread.name": mock.ANY, + } + if send_default_pii: + expected_attrs["url.full"] = "https://bucket.s3.amazonaws.com/foo.pdf" + expected_attrs["url.fragment"] = "" + expected_attrs["url.query"] = "" + assert span1["attributes"] == ApproxDict(expected_attrs) - with sentry_sdk.start_transaction() as transaction, MockResponse( - s3.meta.client, 200, {}, b"hello" - ): - body = obj.get()["Body"] - assert body.read(1) == b"h" - assert body.read(2) == b"el" - assert body.read(3) == b"lo" - assert body.read(1) == b"" - transaction.finish() - - (event,) = events - assert event["type"] == "transaction" - assert len(event["spans"]) == 2 - - span1 = event["spans"][0] - assert span1["op"] == "http.client" - assert span1["description"] == "aws.s3.GetObject" - assert span1["data"] == ApproxDict( - { - "http.method": "GET", - "aws.request.url": "https://bucket.s3.amazonaws.com/foo.pdf", - "http.fragment": "", - "http.query": "", - } - ) + if not send_default_pii: + assert "url.full" not in span1["attributes"] + assert "url.fragment" not in span1["attributes"] + assert "url.query" not in span1["attributes"] - span2 = event["spans"][1] - assert span2["op"] == "http.client.stream" - assert span2["description"] == "aws.s3.GetObject" - assert span2["parent_span_id"] == span1["span_id"] + span2 = spans[1] + assert span2["attributes"]["sentry.op"] == "http.client.stream" + assert span2["name"] == "aws.s3.GetObject" + assert span2["parent_span_id"] == span1["span_id"] -@pytest.mark.parametrize("span_streaming", [True, False]) def test_streaming_close( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( traces_sample_rate=1.0, integrations=[Boto3Integration()], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) s3 = session.resource("s3") obj = s3.Bucket("bucket").Object("foo.pdf") + items = capture_items("span") - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( - s3.meta.client, 200, {}, b"hello" - ): - body = obj.get()["Body"] - assert body.read(1) == b"h" - body.close() # close partially-read stream - span.end() - - sentry_sdk.flush() - spans = [item.payload for item in items] - assert len(spans) == 3 - span1 = spans[0] - assert span1["attributes"]["sentry.op"] == "http.client" - span2 = spans[1] - assert span2["attributes"]["sentry.op"] == "http.client.stream" - else: - events = capture_events() - - with sentry_sdk.start_transaction() as transaction, MockResponse( - s3.meta.client, 200, {}, b"hello" - ): - body = obj.get()["Body"] - assert body.read(1) == b"h" - body.close() # close partially-read stream - transaction.finish() + with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( + s3.meta.client, 200, {}, b"hello" + ): + body = obj.get()["Body"] + assert body.read(1) == b"h" + body.close() # close partially-read stream + span.end() - (event,) = events - assert event["type"] == "transaction" - assert len(event["spans"]) == 2 - span1 = event["spans"][0] - assert span1["op"] == "http.client" - span2 = event["spans"][1] - assert span2["op"] == "http.client.stream" + sentry_sdk.flush() + spans = [item.payload for item in items] + assert len(spans) == 3 + span1 = spans[0] + assert span1["attributes"]["sentry.op"] == "http.client" + span2 = spans[1] + assert span2["attributes"]["sentry.op"] == "http.client.stream" @pytest.mark.tests_internal_exceptions -@pytest.mark.parametrize("span_streaming", [True, False]) def test_omit_url_data_if_parsing_fails( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( traces_sample_rate=1.0, integrations=[Boto3Integration()], send_default_pii=True, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) s3 = session.resource("s3") bucket = s3.Bucket("bucket") + items = capture_items("span") - if span_streaming: - items = capture_items("span") - - with mock.patch( - "sentry_sdk.integrations.boto3.parse_url", - side_effect=ValueError, + with mock.patch( + "sentry_sdk.integrations.boto3.parse_url", + side_effect=ValueError, + ): + with sentry_sdk.traces.start_span(name="custom parent") as span, MockResponse( + s3.meta.client, 200, {}, read_fixture("s3_list.xml") ): - with sentry_sdk.traces.start_span( - name="custom parent" - ) as span, MockResponse( - s3.meta.client, 200, {}, read_fixture("s3_list.xml") - ): - objects = [obj for obj in bucket.objects.all()] - assert len(objects) == 2 - assert objects[0].key == "foo.txt" - assert objects[1].key == "bar.txt" - span.end() - - sentry_sdk.flush() - spans = [item.payload for item in items] - assert spans[0]["attributes"] == ApproxDict( - { - "http.request.method": "GET", - "rpc.method": "S3/ListObjects", - "sentry.environment": "production", - "sentry.op": "http.client", - "sentry.origin": "auto.http.boto3", - "sentry.release": mock.ANY, - "sentry.sdk.name": "sentry.python", - "sentry.sdk.version": mock.ANY, - "sentry.segment.id": mock.ANY, - "sentry.segment.name": "custom parent", - "server.address": mock.ANY, - "thread.id": mock.ANY, - "thread.name": mock.ANY, - } - ) - - assert "url.full" not in spans[0]["attributes"] - assert "url.fragment" not in spans[0]["attributes"] - assert "url.query" not in spans[0]["attributes"] - else: - events = capture_events() + objects = [obj for obj in bucket.objects.all()] + assert len(objects) == 2 + assert objects[0].key == "foo.txt" + assert objects[1].key == "bar.txt" + span.end() + + sentry_sdk.flush() + spans = [item.payload for item in items] + assert spans[0]["attributes"] == ApproxDict( + { + "http.request.method": "GET", + "rpc.method": "S3/ListObjects", + "sentry.environment": "production", + "sentry.op": "http.client", + "sentry.origin": "auto.http.boto3", + "sentry.release": mock.ANY, + "sentry.sdk.name": "sentry.python", + "sentry.sdk.version": mock.ANY, + "sentry.segment.id": mock.ANY, + "sentry.segment.name": "custom parent", + "server.address": mock.ANY, + "thread.id": mock.ANY, + "thread.name": mock.ANY, + } + ) + + assert "url.full" not in spans[0]["attributes"] + assert "url.fragment" not in spans[0]["attributes"] + assert "url.query" not in spans[0]["attributes"] + - with mock.patch( - "sentry_sdk.integrations.boto3.parse_url", - side_effect=ValueError, - ): - with sentry_sdk.start_transaction() as transaction, MockResponse( - s3.meta.client, 200, {}, read_fixture("s3_list.xml") - ): - items = [obj for obj in bucket.objects.all()] - assert len(items) == 2 - assert items[0].key == "foo.txt" - assert items[1].key == "bar.txt" - transaction.finish() - - (event,) = events - assert event["spans"][0]["data"] == ApproxDict( - { - "http.method": "GET", - # no url data - } - ) - - assert "aws.request.url" not in event["spans"][0]["data"] - assert "http.fragment" not in event["spans"][0]["data"] - assert "http.query" not in event["spans"][0]["data"] - - -@pytest.mark.parametrize("span_streaming", [True, False]) def test_span_origin( sentry_init, - capture_events, capture_items, - span_streaming, ): sentry_init( traces_sample_rate=1.0, integrations=[Boto3Integration()], - trace_lifecycle="stream" if span_streaming else "static", - ) - - s3 = session.resource("s3") - bucket = s3.Bucket("bucket") - - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"), MockResponse( - s3.meta.client, 200, {}, read_fixture("s3_list.xml") - ): - _ = [obj for obj in bucket.objects.all()] - - sentry_sdk.flush() - spans = [item.payload for item in items] - - assert spans[1]["attributes"]["sentry.origin"] == "manual" - assert spans[0]["attributes"]["sentry.origin"] == "auto.http.boto3" - else: - events = capture_events() - - with sentry_sdk.start_transaction(), MockResponse( - s3.meta.client, 200, {}, read_fixture("s3_list.xml") - ): - _ = [obj for obj in bucket.objects.all()] - - (event,) = events - - assert event["contexts"]["trace"]["origin"] == "manual" - assert event["spans"][0]["origin"] == "auto.http.boto3" - - -def test_breadcrumb(sentry_init, capture_events): - sentry_init( - integrations=[Boto3Integration()], - default_integrations=False, + trace_lifecycle="stream", ) s3 = session.resource("s3") bucket = s3.Bucket("bucket") + items = capture_items("span") - events = capture_events() - - with MockResponse(s3.meta.client, 200, {}, read_fixture("s3_list.xml")): + with sentry_sdk.traces.start_span(name="custom parent"), MockResponse( + s3.meta.client, 200, {}, read_fixture("s3_list.xml") + ): _ = [obj for obj in bucket.objects.all()] - capture_message("Testing!") + sentry_sdk.flush() + spans = [item.payload for item in items] - (event,) = events - (crumb,) = event["breadcrumbs"]["values"] - assert crumb["type"] == "http" - assert crumb["category"] == "httplib" - assert crumb["data"] == ApproxDict( - { - "aws.request.url": mock.ANY, - SPANDATA.HTTP_METHOD: "GET", - SPANDATA.HTTP_QUERY: mock.ANY, - SPANDATA.HTTP_FRAGMENT: "", - } - ) + assert spans[1]["attributes"]["sentry.origin"] == "manual" + assert spans[0]["attributes"]["sentry.origin"] == "auto.http.boto3" @pytest.mark.parametrize("send_default_pii", [True, False]) -def test_breadcrumb_span_streaming(sentry_init, capture_events, send_default_pii): +def test_breadcrumb(sentry_init, capture_events, send_default_pii): sentry_init( integrations=[Boto3Integration()], default_integrations=False, diff --git a/tests/integrations/boto3/test_trace_propagation.py b/tests/integrations/boto3/test_trace_propagation.py index 85d6544c63..b04af56855 100644 --- a/tests/integrations/boto3/test_trace_propagation.py +++ b/tests/integrations/boto3/test_trace_propagation.py @@ -3,7 +3,6 @@ from urllib.parse import parse_qs, urlparse import boto3 -import pytest from botocore.config import Config import sentry_sdk @@ -35,11 +34,12 @@ def _start_server(): return server, thread -@pytest.mark.parametrize("span_streaming", [False, True]) -def test_botocore_merges_propagation_before_sigv4_signing(sentry_init, span_streaming): +def test_botocore_merges_propagation_before_sigv4_signing( + sentry_init, +): sentry_init( traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", default_integrations=False, integrations=[Boto3Integration(), StdlibIntegration()], ) @@ -76,21 +76,13 @@ def capture_headers_after_instrumentation(request, **kwargs): client.meta.events.register_last( "before-sign", capture_headers_after_instrumentation ) - - if span_streaming: - with sentry_sdk.traces.start_span( # type: ignore[attr-defined] - name="incoming" - ): - response = client.head_object( - Bucket="example-bucket", - Key="example-key", - ) - else: - with sentry_sdk.start_transaction(name="incoming", sampled=True): - response = client.head_object( - Bucket="example-bucket", - Key="example-key", - ) + with sentry_sdk.traces.start_span( # type: ignore[attr-defined] + name="incoming" + ): + response = client.head_object( + Bucket="example-bucket", + Key="example-key", + ) assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 headers = _AwsRequestHandler.requests[-1] @@ -127,14 +119,13 @@ def capture_headers_after_instrumentation(request, **kwargs): thread.join() -@pytest.mark.parametrize("span_streaming", [False, True]) def test_botocore_without_boto3_integration_preserves_signed_baggage( - sentry_init, span_streaming + sentry_init, ): """Leave signed `baggage` as-is; add unsigned `sentry-trace`.""" sentry_init( traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", default_integrations=False, integrations=[StdlibIntegration()], ) @@ -154,21 +145,13 @@ def _inject_signed_baggage(request, **kwargs): # inject `baggage` before SigV4; stdlib-only path cannot change signed fields. client.meta.events.register("before-sign", _inject_signed_baggage) - - if span_streaming: - with sentry_sdk.traces.start_span( # type: ignore[attr-defined] - name="incoming" - ): - response = client.head_object( - Bucket="example-bucket", - Key="example-key", - ) - else: - with sentry_sdk.start_transaction(name="incoming", sampled=True): - response = client.head_object( - Bucket="example-bucket", - Key="example-key", - ) + with sentry_sdk.traces.start_span( # type: ignore[attr-defined] + name="incoming" + ): + response = client.head_object( + Bucket="example-bucket", + Key="example-key", + ) assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 headers = _AwsRequestHandler.requests[-1]