From 86b559906c14b1d8253dab1023372912062ce983 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 31 Aug 2026 10:07:22 +0200 Subject: [PATCH 1/2] chore(pyreqwest): Remove transaction-based tracing --- sentry_sdk/integrations/pyreqwest.py | 109 +-- .../integrations/pyreqwest/test_pyreqwest.py | 837 ++++++------------ 2 files changed, 286 insertions(+), 660 deletions(-) diff --git a/sentry_sdk/integrations/pyreqwest.py b/sentry_sdk/integrations/pyreqwest.py index 52f62b9074..e126a73786 100644 --- a/sentry_sdk/integrations/pyreqwest.py +++ b/sentry_sdk/integrations/pyreqwest.py @@ -2,24 +2,17 @@ from typing import Any, Generator import sentry_sdk -from sentry_sdk import start_span from sentry_sdk.consts import OP, SPANDATA 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 from sentry_sdk.tracing_utils import ( add_http_breadcrumb, add_http_request_source, - add_sentry_baggage_to_headers, - has_span_streaming_enabled, propagate_trace_headers, - should_propagate_trace, ) from sentry_sdk.utils import ( SENSITIVE_DATA_SUBSTITUTE, capture_internal_exceptions, - logger, parse_url, parse_version, ) @@ -99,67 +92,31 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]": with capture_internal_exceptions(): parsed_url = parse_url(str(request.url), sanitize=False) - span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) - if span_streaming: - if sentry_sdk.traces.get_current_span() is None: - propagate_trace_headers(client=sentry_sdk.get_client(), request=request) - yield None - return - - with sentry_sdk.traces.start_span( - name=f"{request.method} {parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE}", - attributes={ - "sentry.op": OP.HTTP_CLIENT, - "sentry.origin": PyreqwestIntegration.origin, - SPANDATA.HTTP_REQUEST_METHOD: request.method, - }, - ) as span: - if parsed_url is not None and should_send_default_pii(): - span.set_attribute(SPANDATA.URL_FULL, parsed_url.url) - span.set_attribute(SPANDATA.URL_QUERY, parsed_url.query) - span.set_attribute(SPANDATA.URL_FRAGMENT, parsed_url.fragment) - - propagate_trace_headers(client=sentry_sdk.get_client(), request=request) - - yield span - - if span is not None: - with capture_internal_exceptions(): - add_http_request_source(span) - - return - - with start_span( - op=OP.HTTP_CLIENT, + if sentry_sdk.traces.get_current_span() is None: + propagate_trace_headers(client=sentry_sdk.get_client(), request=request) + yield None + return + + with sentry_sdk.traces.start_span( name=f"{request.method} {parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE}", - origin=PyreqwestIntegration.origin, + attributes={ + "sentry.op": OP.HTTP_CLIENT, + "sentry.origin": PyreqwestIntegration.origin, + SPANDATA.HTTP_REQUEST_METHOD: request.method, + }, ) as span: - span.set_data(SPANDATA.HTTP_METHOD, request.method) - if parsed_url is not None: - span.set_data("url", parsed_url.url) - span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) - span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) - - if should_propagate_trace(sentry_sdk.get_client(), str(request.url)): - for ( - key, - value, - ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(): - logger.debug( - "[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format( - key=key, value=value, url=request.url - ) - ) - - if key == BAGGAGE_HEADER_NAME: - add_sentry_baggage_to_headers(request.headers, value) - else: - request.headers[key] = value + if parsed_url is not None and should_send_default_pii(): + span.set_attribute(SPANDATA.URL_FULL, parsed_url.url) + span.set_attribute(SPANDATA.URL_QUERY, parsed_url.query) + span.set_attribute(SPANDATA.URL_FRAGMENT, parsed_url.fragment) + + propagate_trace_headers(client=sentry_sdk.get_client(), request=request) yield span - with capture_internal_exceptions(): - add_http_request_source(span) + if span is not None: + with capture_internal_exceptions(): + add_http_request_source(span) async def sentry_async_middleware( @@ -179,14 +136,11 @@ async def sentry_async_middleware( response = None with _sentry_pyreqwest_span(request) as span: response = await next_handler.run(request) - if isinstance(span, StreamedSpan): - span.status = "error" if response.status >= 400 else "ok" - span.set_attribute( - SPANDATA.HTTP_STATUS_CODE, - response.status, - ) - elif span is not None: - span.set_http_status(response.status) + span.status = "error" if response.status >= 400 else "ok" + span.set_attribute( + SPANDATA.HTTP_STATUS_CODE, + response.status, + ) if response is not None: breadcrumb_data = { @@ -224,14 +178,11 @@ def sentry_sync_middleware( response = None with _sentry_pyreqwest_span(request) as span: response = next_handler.run(request) - if isinstance(span, StreamedSpan): - span.status = "error" if response.status >= 400 else "ok" - span.set_attribute( - SPANDATA.HTTP_STATUS_CODE, - response.status, - ) - elif span is not None: - span.set_http_status(response.status) + span.status = "error" if response.status >= 400 else "ok" + span.set_attribute( + SPANDATA.HTTP_STATUS_CODE, + response.status, + ) if response is not None: breadcrumb_data = { diff --git a/tests/integrations/pyreqwest/test_pyreqwest.py b/tests/integrations/pyreqwest/test_pyreqwest.py index b31f717e6d..8e7411adae 100644 --- a/tests/integrations/pyreqwest/test_pyreqwest.py +++ b/tests/integrations/pyreqwest/test_pyreqwest.py @@ -60,406 +60,260 @@ def clear_captured_requests(): @pytest.mark.parametrize("send_default_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_sync_client_spans( sentry_init, - capture_events, capture_items, server_port, - span_streaming, send_default_pii, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello?q=test#frag" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - client = SyncClientBuilder().build() - response = client.get(url).build().send() - assert response.status == 200 + with sentry_sdk.traces.start_span(name="custom parent"): + client = SyncClientBuilder().build() + response = client.get(url).build().send() + assert response.status == 200 + + 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"] == f"GET http://localhost:{server_port}/hello" + assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" + assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 + assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - 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"] == f"GET http://localhost:{server_port}/hello" - assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" - assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - - if send_default_pii: - assert ( - span["attributes"]["url.full"] - == f"http://localhost:{server_port}/hello" - ) - assert span["attributes"][SPANDATA.URL_QUERY] == "q=test" - assert span["attributes"][SPANDATA.URL_FRAGMENT] == "frag" - else: - assert "url.full" not in span["attributes"] - assert SPANDATA.URL_QUERY not in span["attributes"] - assert SPANDATA.URL_FRAGMENT not in span["attributes"] + if send_default_pii: + assert span["attributes"]["url.full"] == f"http://localhost:{server_port}/hello" + assert span["attributes"][SPANDATA.URL_QUERY] == "q=test" + assert span["attributes"][SPANDATA.URL_FRAGMENT] == "frag" else: - events = capture_events() - - with start_transaction(name="test_transaction"): - client = SyncClientBuilder().build() - response = client.get(url).build().send() - assert response.status == 200 - - (event,) = events - assert len(event["spans"]) == 1 - span = event["spans"][0] - assert span["op"] == "http.client" - assert span["description"] == f"GET http://localhost:{server_port}/hello" - assert span["data"]["url"] == f"http://localhost:{server_port}/hello" - assert span["data"][SPANDATA.HTTP_METHOD] == "GET" - assert span["data"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["data"][SPANDATA.HTTP_QUERY] == "q=test" - assert span["data"][SPANDATA.HTTP_FRAGMENT] == "frag" - assert span["origin"] == "auto.http.pyreqwest" + assert "url.full" not in span["attributes"] + assert SPANDATA.URL_QUERY not in span["attributes"] + assert SPANDATA.URL_FRAGMENT not in span["attributes"] @pytest.mark.asyncio @pytest.mark.parametrize("send_default_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) async def test_async_client_spans( sentry_init, - capture_events, capture_items, server_port, - span_streaming, send_default_pii, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - async with ClientBuilder().build() as client: - with sentry_sdk.traces.start_span(name="custom parent"): - response = await client.get(url).build().send() - assert response.status == 200 - - 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"] == f"GET {url}" - assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" - assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - - if send_default_pii: - assert span["attributes"]["url.full"] == url - else: - assert "url.full" not in span["attributes"] - else: - events = capture_events() + async with ClientBuilder().build() as client: + with sentry_sdk.traces.start_span(name="custom parent"): + response = await client.get(url).build().send() + assert response.status == 200 - async with ClientBuilder().build() as client: - with start_transaction(name="test_transaction"): - response = await client.get(url).build().send() - assert response.status == 200 + 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"] == f"GET {url}" + assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" + assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 + assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - (event,) = events - assert len(event["spans"]) == 1 - span = event["spans"][0] - assert span["op"] == "http.client" - assert span["description"] == f"GET {url}" - assert span["data"]["url"] == url - assert span["data"][SPANDATA.HTTP_METHOD] == "GET" - assert span["data"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["origin"] == "auto.http.pyreqwest" + if send_default_pii: + assert span["attributes"]["url.full"] == url + else: + assert "url.full" not in span["attributes"] @pytest.mark.parametrize("send_default_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_sync_simple_request_spans( sentry_init, - capture_events, capture_items, server_port, - span_streaming, send_default_pii, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello-simple" - if span_streaming: - items = capture_items("span") + items = capture_items("span") + + with sentry_sdk.traces.start_span(name="custom parent"): + response = sync_pyreqwest_get(url).send() + assert response.status == 200 + + 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"] == f"GET {url}" + assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" + assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 + assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - with sentry_sdk.traces.start_span(name="custom parent"): - response = sync_pyreqwest_get(url).send() - assert response.status == 200 - - 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"] == f"GET {url}" - assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" - assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - - if send_default_pii: - assert span["attributes"]["url.full"] == url - else: - assert "url.full" not in span["attributes"] + if send_default_pii: + assert span["attributes"]["url.full"] == url else: - events = capture_events() - - with start_transaction(name="test_transaction"): - response = sync_pyreqwest_get(url).send() - assert response.status == 200 - - (event,) = events - assert len(event["spans"]) == 1 - span = event["spans"][0] - assert span["op"] == "http.client" - assert span["description"] == f"GET {url}" - assert span["data"]["url"] == url - assert span["data"][SPANDATA.HTTP_METHOD] == "GET" - assert span["data"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["origin"] == "auto.http.pyreqwest" + assert "url.full" not in span["attributes"] @pytest.mark.asyncio @pytest.mark.parametrize("send_default_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) async def test_async_simple_request_spans( sentry_init, - capture_events, capture_items, server_port, - span_streaming, send_default_pii, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello-simple-async" - if span_streaming: - items = capture_items("span") + items = capture_items("span") + + with sentry_sdk.traces.start_span(name="custom parent"): + response = await async_pyreqwest_get(url).send() + assert response.status == 200 + + 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"] == f"GET {url}" + assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" + assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 + assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - with sentry_sdk.traces.start_span(name="custom parent"): - response = await async_pyreqwest_get(url).send() - assert response.status == 200 - - 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"] == f"GET {url}" - assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" - assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - - if send_default_pii: - assert span["attributes"]["url.full"] == url - else: - assert "url.full" not in span["attributes"] + if send_default_pii: + assert span["attributes"]["url.full"] == url else: - events = capture_events() - - with start_transaction(name="test_transaction"): - response = await async_pyreqwest_get(url).send() - assert response.status == 200 - - (event,) = events - assert len(event["spans"]) == 1 - span = event["spans"][0] - assert span["op"] == "http.client" - assert span["description"] == f"GET {url}" - assert span["data"]["url"] == url - assert span["data"][SPANDATA.HTTP_METHOD] == "GET" - assert span["data"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert span["origin"] == "auto.http.pyreqwest" + assert "url.full" not in span["attributes"] -@pytest.mark.parametrize("span_streaming", [True, False]) def test_span_origin( sentry_init, - capture_events, capture_items, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/origin" - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - assert spans[0]["attributes"]["sentry.origin"] == "auto.http.pyreqwest" - else: - events = capture_events() + items = capture_items("span") - with start_transaction(name="test_transaction"): - client = SyncClientBuilder().build() - client.get(url).build().send() + with sentry_sdk.traces.start_span(name="custom parent"): + client = SyncClientBuilder().build() + client.get(url).build().send() - (event,) = events - assert event["spans"][0]["origin"] == "auto.http.pyreqwest" + sentry_sdk.flush() + spans = [item.payload for item in items] + assert spans[0]["attributes"]["sentry.origin"] == "auto.http.pyreqwest" -@pytest.mark.parametrize("span_streaming", [True, False]) def test_outgoing_trace_headers( sentry_init, server_port, capture_items, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, trace_propagation_targets=["localhost"], - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/trace" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span( - name="custom parent", - ): - client = SyncClientBuilder().build() - response = client.get(url).build().send() - assert response.status == 200 - - assert len(PyreqwestMockHandler.captured_requests) == 1 - headers = PyreqwestMockHandler.captured_requests[0]["headers"] - - sentry_sdk.flush() - spans = [item.payload for item in items] - http_span = next( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.client" - ) + with sentry_sdk.traces.start_span( + name="custom parent", + ): + client = SyncClientBuilder().build() + response = client.get(url).build().send() + assert response.status == 200 - assert "sentry-trace" in headers - assert headers["sentry-trace"].startswith(http_span["trace_id"]) - assert "baggage" in headers - assert f"sentry-trace_id={http_span['trace_id']}" in headers["baggage"] - else: - with start_transaction( - name="test_transaction", trace_id="01234567890123456789012345678901" - ): - client = SyncClientBuilder().build() - response = client.get(url).build().send() - assert response.status == 200 + assert len(PyreqwestMockHandler.captured_requests) == 1 + headers = PyreqwestMockHandler.captured_requests[0]["headers"] - assert len(PyreqwestMockHandler.captured_requests) == 1 - headers = PyreqwestMockHandler.captured_requests[0]["headers"] + sentry_sdk.flush() + spans = [item.payload for item in items] + http_span = next( + span for span in spans if span["attributes"].get("sentry.op") == "http.client" + ) - assert "sentry-trace" in headers - assert headers["sentry-trace"].startswith("01234567890123456789012345678901") - assert "baggage" in headers - assert "sentry-trace_id=01234567890123456789012345678901" in headers["baggage"] + assert "sentry-trace" in headers + assert headers["sentry-trace"].startswith(http_span["trace_id"]) + assert "baggage" in headers + assert f"sentry-trace_id={http_span['trace_id']}" in headers["baggage"] -@pytest.mark.parametrize("span_streaming", [True, False]) def test_outgoing_trace_headers_append_to_baggage( sentry_init, server_port, capture_items, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, trace_propagation_targets=["localhost"], release="d08ebdb9309e1b004c6f52202de58a09c2268e42", - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/baggage" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with mock.patch( - "sentry_sdk.tracing_utils.Random.randrange", return_value=500000 + with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000): + with sentry_sdk.traces.start_span( + name="/interactions/other-dogs/new-dog", + attributes={ + "sentry.op": "greeting.sniff", + }, ): - with sentry_sdk.traces.start_span( - name="/interactions/other-dogs/new-dog", - attributes={ - "sentry.op": "greeting.sniff", - }, - ): - client = SyncClientBuilder().build() - client.get(url).header("baggage", "custom=data").build().send() - - assert len(PyreqwestMockHandler.captured_requests) == 1 - headers = PyreqwestMockHandler.captured_requests[0]["headers"] - - sentry_sdk.flush() - spans = [item.payload for item in items] - http_span = next( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.client" - ) + client = SyncClientBuilder().build() + client.get(url).header("baggage", "custom=data").build().send() - assert "baggage" in headers - baggage = headers["baggage"] - assert "custom=data" in baggage - assert f"sentry-trace_id={http_span['trace_id']}" in baggage - else: - with mock.patch( - "sentry_sdk.tracing_utils.Random.randrange", return_value=500000 - ): - with start_transaction( - name="/interactions/other-dogs/new-dog", - op="greeting.sniff", - trace_id="01234567890123456789012345678901", - ): - client = SyncClientBuilder().build() - client.get(url).header("baggage", "custom=data").build().send() - - assert len(PyreqwestMockHandler.captured_requests) == 1 - headers = PyreqwestMockHandler.captured_requests[0]["headers"] - - assert "baggage" in headers - baggage = headers["baggage"] - assert "custom=data" in baggage - assert "sentry-trace_id=01234567890123456789012345678901" in baggage + assert len(PyreqwestMockHandler.captured_requests) == 1 + headers = PyreqwestMockHandler.captured_requests[0]["headers"] + + sentry_sdk.flush() + spans = [item.payload for item in items] + http_span = next( + span for span in spans if span["attributes"].get("sentry.op") == "http.client" + ) + + assert "baggage" in headers + baggage = headers["baggage"] + assert "custom=data" in baggage + assert f"sentry-trace_id={http_span['trace_id']}" in baggage assert "sentry-sample_rand=0.500000" in baggage assert "sentry-environment=production" in baggage assert "sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42" in baggage @@ -478,19 +332,17 @@ def test_outgoing_trace_headers_append_to_baggage( [[r"https?:\/\/[\w\-]+(\.[\w\-]+)+\.net"], False], ], ) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_trace_propagation_targets( sentry_init, server_port, trace_propagation_targets, trace_propagated, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], trace_propagation_targets=trace_propagation_targets, traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/propagation" @@ -509,128 +361,83 @@ def test_trace_propagation_targets( @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, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/parse-fail" - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"): - with mock.patch( - "sentry_sdk.integrations.pyreqwest.parse_url", - side_effect=ValueError, - ): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - - assert span["name"] == "GET [Filtered]" - assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" - assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert "url.full" not in span["attributes"] - assert SPANDATA.URL_QUERY not in span["attributes"] - assert SPANDATA.URL_FRAGMENT not in span["attributes"] - else: - events = capture_events() + items = capture_items("span") - with start_transaction(name="test_transaction"): - with mock.patch( - "sentry_sdk.integrations.pyreqwest.parse_url", - side_effect=ValueError, - ): - client = SyncClientBuilder().build() - client.get(url).build().send() + with sentry_sdk.traces.start_span(name="custom parent"): + with mock.patch( + "sentry_sdk.integrations.pyreqwest.parse_url", + side_effect=ValueError, + ): + client = SyncClientBuilder().build() + client.get(url).build().send() - (event,) = events - span = event["spans"][0] + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] - assert span["description"] == "GET [Filtered]" - assert span["data"][SPANDATA.HTTP_METHOD] == "GET" - assert span["data"][SPANDATA.HTTP_STATUS_CODE] == 200 - assert "url" not in span["data"] - assert SPANDATA.HTTP_QUERY not in span["data"] - assert SPANDATA.HTTP_FRAGMENT not in span["data"] + assert span["name"] == "GET [Filtered]" + assert span["attributes"][SPANDATA.HTTP_REQUEST_METHOD] == "GET" + assert span["attributes"][SPANDATA.HTTP_STATUS_CODE] == 200 + assert "url.full" not in span["attributes"] + assert SPANDATA.URL_QUERY not in span["attributes"] + assert SPANDATA.URL_FRAGMENT not in span["attributes"] -@pytest.mark.parametrize("span_streaming", [True, False]) def test_request_source_disabled( sentry_init, - capture_events, capture_items, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, enable_http_request_source=False, http_request_source_threshold_ms=0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - data = span.get("attributes", {}) - - assert SPANDATA.CODE_LINE_NUMBER not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILE_PATH not in data - else: - events = capture_events() - - with start_transaction(name="test_transaction"): - client = SyncClientBuilder().build() - client.get(url).build().send() + with sentry_sdk.traces.start_span(name="custom parent"): + client = SyncClientBuilder().build() + client.get(url).build().send() - (event,) = events - span = event["spans"][0] - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] + data = span.get("attributes", {}) - assert SPANDATA.CODE_LINENO not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILEPATH not in data + assert SPANDATA.CODE_LINE_NUMBER not in data + assert SPANDATA.CODE_NAMESPACE not in data + assert SPANDATA.CODE_FILE_PATH not in data assert SPANDATA.CODE_FUNCTION not in data @pytest.mark.parametrize("enable_http_request_source", [None, True]) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_request_source_enabled( sentry_init, - capture_events, capture_items, server_port, enable_http_request_source, - span_streaming, ): sentry_options = { "integrations": [PyreqwestIntegration()], "traces_sample_rate": 1.0, "http_request_source_threshold_ms": 0, - "trace_lifecycle": "stream" if span_streaming else "static", + "trace_lifecycle": "stream", } if enable_http_request_source is not None: sentry_options["enable_http_request_source"] = enable_http_request_source @@ -638,334 +445,202 @@ def test_request_source_enabled( sentry_init(**sentry_options) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - data = span.get("attributes", {}) - - assert SPANDATA.CODE_LINE_NUMBER in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILE_PATH in data - else: - events = capture_events() + items = capture_items("span") - with start_transaction(name="test_transaction"): - client = SyncClientBuilder().build() - client.get(url).build().send() + with sentry_sdk.traces.start_span(name="custom parent"): + client = SyncClientBuilder().build() + client.get(url).build().send() - (event,) = events - span = event["spans"][0] - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] + data = span.get("attributes", {}) - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data + assert SPANDATA.CODE_LINE_NUMBER in data + assert SPANDATA.CODE_NAMESPACE in data + assert SPANDATA.CODE_FILE_PATH in data assert SPANDATA.CODE_FUNCTION in data -@pytest.mark.parametrize("span_streaming", [True, False]) def test_request_source( sentry_init, - capture_events, capture_items, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, enable_http_request_source=True, http_request_source_threshold_ms=0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - data = span.get("attributes", {}) + items = capture_items("span") - assert type(data.get(SPANDATA.CODE_LINE_NUMBER)) == int - assert data.get(SPANDATA.CODE_LINE_NUMBER) > 0 - assert ( - data.get(SPANDATA.CODE_NAMESPACE) - == "tests.integrations.pyreqwest.test_pyreqwest" - ) - assert data.get(SPANDATA.CODE_FILE_PATH).endswith( - "tests/integrations/pyreqwest/test_pyreqwest.py" - ) - - is_relative_path = data.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep - else: - events = capture_events() - - with start_transaction(name="test_transaction"): - client = SyncClientBuilder().build() - client.get(url).build().send() + with sentry_sdk.traces.start_span(name="custom parent"): + client = SyncClientBuilder().build() + client.get(url).build().send() - (event,) = events - span = event["spans"][0] - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] + data = span.get("attributes", {}) - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 - assert ( - data.get(SPANDATA.CODE_NAMESPACE) - == "tests.integrations.pyreqwest.test_pyreqwest" - ) - assert data.get(SPANDATA.CODE_FILEPATH).endswith( - "tests/integrations/pyreqwest/test_pyreqwest.py" - ) + assert type(data.get(SPANDATA.CODE_LINE_NUMBER)) == int + assert data.get(SPANDATA.CODE_LINE_NUMBER) > 0 + assert ( + data.get(SPANDATA.CODE_NAMESPACE) + == "tests.integrations.pyreqwest.test_pyreqwest" + ) + assert data.get(SPANDATA.CODE_FILE_PATH).endswith( + "tests/integrations/pyreqwest/test_pyreqwest.py" + ) - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep + is_relative_path = data.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep assert is_relative_path assert data.get(SPANDATA.CODE_FUNCTION) == "test_request_source" -@pytest.mark.parametrize("span_streaming", [True, False]) def test_request_source_with_module_in_search_path( sentry_init, - capture_events, capture_items, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, enable_http_request_source=True, http_request_source_threshold_ms=0, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") - - with sentry_sdk.traces.start_span(name="custom parent"): - from pyreqwest_helpers.helpers import get_request_with_client - - client = SyncClientBuilder().build() - get_request_with_client(client, url) - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - data = span.get("attributes", {}) - - assert type(data.get(SPANDATA.CODE_LINE_NUMBER)) == int - assert data.get(SPANDATA.CODE_LINE_NUMBER) > 0 - assert data.get(SPANDATA.CODE_NAMESPACE) == "pyreqwest_helpers.helpers" - assert data.get(SPANDATA.CODE_FILE_PATH) == "pyreqwest_helpers/helpers.py" - - is_relative_path = data.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep - else: - events = capture_events() + items = capture_items("span") - with start_transaction(name="test_transaction"): - from pyreqwest_helpers.helpers import get_request_with_client + with sentry_sdk.traces.start_span(name="custom parent"): + from pyreqwest_helpers.helpers import get_request_with_client - client = SyncClientBuilder().build() - get_request_with_client(client, url) + client = SyncClientBuilder().build() + get_request_with_client(client, url) - (event,) = events - span = event["spans"][0] - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] + data = span.get("attributes", {}) - assert type(data.get(SPANDATA.CODE_LINENO)) == int - assert data.get(SPANDATA.CODE_LINENO) > 0 - assert data.get(SPANDATA.CODE_NAMESPACE) == "pyreqwest_helpers.helpers" - assert data.get(SPANDATA.CODE_FILEPATH) == "pyreqwest_helpers/helpers.py" + assert type(data.get(SPANDATA.CODE_LINE_NUMBER)) == int + assert data.get(SPANDATA.CODE_LINE_NUMBER) > 0 + assert data.get(SPANDATA.CODE_NAMESPACE) == "pyreqwest_helpers.helpers" + assert data.get(SPANDATA.CODE_FILE_PATH) == "pyreqwest_helpers/helpers.py" - is_relative_path = data.get(SPANDATA.CODE_FILEPATH)[0] != os.sep + is_relative_path = data.get(SPANDATA.CODE_FILE_PATH)[0] != os.sep assert is_relative_path assert data.get(SPANDATA.CODE_FUNCTION) == "get_request_with_client" -@pytest.mark.parametrize("span_streaming", [True, False]) def test_no_request_source_if_duration_too_short( sentry_init, - capture_events, capture_items, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, enable_http_request_source=True, http_request_source_threshold_ms=100, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - original_start_span = sentry_sdk.traces.start_span - - @contextmanager - def fake_start_span(*args, **kwargs): - with original_start_span(*args, **kwargs) as span: - span._start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) - span._end_timestamp = datetime.datetime( - 2024, 1, 1, microsecond=99999 - ) - - yield span - - span._end_timestamp = None - - with mock.patch( - "sentry_sdk.integrations.pyreqwest.sentry_sdk.traces.start_span", - fake_start_span, - ): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - data = span.get("attributes", {}) - - assert SPANDATA.CODE_LINE_NUMBER not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILE_PATH not in data - else: - events = capture_events() + with sentry_sdk.traces.start_span(name="custom parent"): + original_start_span = sentry_sdk.traces.start_span - with start_transaction(name="test_transaction"): + @contextmanager + def fake_start_span(*args, **kwargs): + with original_start_span(*args, **kwargs) as span: + span._start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) + span._end_timestamp = datetime.datetime(2024, 1, 1, microsecond=99999) - @contextmanager - def fake_start_span(*args, **kwargs): - with sentry_sdk.start_span(*args, **kwargs) as span: - pass - span.start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) - span.timestamp = datetime.datetime(2024, 1, 1, microsecond=99999) yield span - with mock.patch( - "sentry_sdk.integrations.pyreqwest.start_span", - fake_start_span, - ): - client = SyncClientBuilder().build() - client.get(url).build().send() + span._end_timestamp = None + + with mock.patch( + "sentry_sdk.integrations.pyreqwest.sentry_sdk.traces.start_span", + fake_start_span, + ): + client = SyncClientBuilder().build() + client.get(url).build().send() - (event,) = events - span = event["spans"][-1] - data = span.get("data", {}) + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] + data = span.get("attributes", {}) - assert SPANDATA.CODE_LINENO not in data - assert SPANDATA.CODE_NAMESPACE not in data - assert SPANDATA.CODE_FILEPATH not in data + assert SPANDATA.CODE_LINE_NUMBER not in data + assert SPANDATA.CODE_NAMESPACE not in data + assert SPANDATA.CODE_FILE_PATH not in data assert SPANDATA.CODE_FUNCTION not in data -@pytest.mark.parametrize("span_streaming", [True, False]) def test_request_source_if_duration_over_threshold( sentry_init, - capture_events, capture_items, server_port, - span_streaming, ): sentry_init( integrations=[PyreqwestIntegration()], traces_sample_rate=1.0, enable_http_request_source=True, http_request_source_threshold_ms=100, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello" - if span_streaming: - items = capture_items("span") + items = capture_items("span") - with sentry_sdk.traces.start_span(name="custom parent"): - original_start_span = sentry_sdk.traces.start_span - - @contextmanager - def fake_start_span(*args, **kwargs): - with original_start_span(*args, **kwargs) as span: - span._start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) - span._end_timestamp = datetime.datetime( - 2024, 1, 1, microsecond=100001 - ) - - yield span - - span._end_timestamp = None - - with mock.patch( - "sentry_sdk.integrations.pyreqwest.sentry_sdk.traces.start_span", - fake_start_span, - ): - client = SyncClientBuilder().build() - client.get(url).build().send() - - sentry_sdk.flush() - spans = [item.payload for item in items] - span = spans[0] - data = span.get("attributes", {}) - - assert SPANDATA.CODE_LINE_NUMBER in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILE_PATH in data - else: - events = capture_events() + with sentry_sdk.traces.start_span(name="custom parent"): + original_start_span = sentry_sdk.traces.start_span - with start_transaction(name="test_transaction"): + @contextmanager + def fake_start_span(*args, **kwargs): + with original_start_span(*args, **kwargs) as span: + span._start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) + span._end_timestamp = datetime.datetime(2024, 1, 1, microsecond=100001) - @contextmanager - def fake_start_span(*args, **kwargs): - with sentry_sdk.start_span(*args, **kwargs) as span: - pass - span.start_timestamp = datetime.datetime(2024, 1, 1, microsecond=0) - span.timestamp = datetime.datetime(2024, 1, 1, microsecond=100001) yield span - with mock.patch( - "sentry_sdk.integrations.pyreqwest.start_span", - fake_start_span, - ): - client = SyncClientBuilder().build() - client.get(url).build().send() + span._end_timestamp = None - (event,) = events - span = event["spans"][-1] - data = span.get("data", {}) + with mock.patch( + "sentry_sdk.integrations.pyreqwest.sentry_sdk.traces.start_span", + fake_start_span, + ): + client = SyncClientBuilder().build() + client.get(url).build().send() - assert SPANDATA.CODE_LINENO in data - assert SPANDATA.CODE_NAMESPACE in data - assert SPANDATA.CODE_FILEPATH in data + sentry_sdk.flush() + spans = [item.payload for item in items] + span = spans[0] + data = span.get("attributes", {}) + + assert SPANDATA.CODE_LINE_NUMBER in data + assert SPANDATA.CODE_NAMESPACE in data + assert SPANDATA.CODE_FILE_PATH in data assert SPANDATA.CODE_FUNCTION in data @pytest.mark.parametrize("send_default_pii", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) def test_crumb_capture( sentry_init, capture_events, server_port, send_default_pii, - span_streaming, ): def before_breadcrumb(crumb, hint): crumb["data"]["extra"] = "foo" @@ -975,7 +650,7 @@ def before_breadcrumb(crumb, hint): integrations=[PyreqwestIntegration()], before_breadcrumb=before_breadcrumb, send_default_pii=send_default_pii, - trace_lifecycle="stream" if span_streaming else "static", + trace_lifecycle="stream", ) url = f"http://localhost:{server_port}/hello?q=test#frag" From 3655e7c901999bec10957355e2aac1ebc97c3011 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 31 Aug 2026 10:20:40 +0200 Subject: [PATCH 2/2] . --- sentry_sdk/integrations/pyreqwest.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/integrations/pyreqwest.py b/sentry_sdk/integrations/pyreqwest.py index e126a73786..4e02a4086a 100644 --- a/sentry_sdk/integrations/pyreqwest.py +++ b/sentry_sdk/integrations/pyreqwest.py @@ -136,11 +136,12 @@ async def sentry_async_middleware( response = None with _sentry_pyreqwest_span(request) as span: response = await next_handler.run(request) - span.status = "error" if response.status >= 400 else "ok" - span.set_attribute( - SPANDATA.HTTP_STATUS_CODE, - response.status, - ) + if span is not None: + span.status = "error" if response.status >= 400 else "ok" + span.set_attribute( + SPANDATA.HTTP_STATUS_CODE, + response.status, + ) if response is not None: breadcrumb_data = { @@ -178,11 +179,12 @@ def sentry_sync_middleware( response = None with _sentry_pyreqwest_span(request) as span: response = next_handler.run(request) - span.status = "error" if response.status >= 400 else "ok" - span.set_attribute( - SPANDATA.HTTP_STATUS_CODE, - response.status, - ) + if span is not None: + span.status = "error" if response.status >= 400 else "ok" + span.set_attribute( + SPANDATA.HTTP_STATUS_CODE, + response.status, + ) if response is not None: breadcrumb_data = {