Skip to content

Commit 5ba869c

Browse files
sentrivanaclaudeericapisanisl0thentr0py
authored
fix(tracing): Skip child span creation in streaming path when no current span (HTTP clients) (#6811)
## Summary - When span streaming is enabled and there is no current span, HTTP client integrations should not create new root segments for child-span operations - Adds a `sentry_sdk.traces.get_current_span() is None` guard before creating spans in the streaming path - Affected integrations: httpx, httpx2, pyreqwest, boto3, stdlib (urllib) ## Test plan - [ ] Existing tests pass - [ ] Verify that in streaming mode, no orphan root segments are created for outgoing HTTP requests when there's no active span 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Erica Pisani <[email protected]> Co-authored-by: Neel Shah <[email protected]>
1 parent efddaab commit 5ba869c

11 files changed

Lines changed: 488 additions & 323 deletions

File tree

sentry_sdk/integrations/boto3.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def _sentry_request_created(
6767
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
6868
span: "Union[Span, StreamedSpan]"
6969
if is_span_streaming_enabled:
70+
if sentry_sdk.traces.get_current_span() is None:
71+
return
7072
span = sentry_sdk.traces.start_span(
7173
name=description,
7274
attributes={

sentry_sdk/integrations/httpx.py

Lines changed: 13 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,15 @@
44
from sentry_sdk.consts import OP, SPANDATA
55
from sentry_sdk.integrations import DidNotEnable, Integration
66
from sentry_sdk.scope import should_send_default_pii
7-
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME
87
from sentry_sdk.tracing_utils import (
98
add_http_request_source,
10-
add_sentry_baggage_to_headers,
119
has_span_streaming_enabled,
12-
should_propagate_trace,
10+
propagate_trace_headers,
1311
)
1412
from sentry_sdk.utils import (
1513
SENSITIVE_DATA_SUBSTITUTE,
1614
capture_internal_exceptions,
1715
ensure_integration_enabled,
18-
logger,
1916
parse_url,
2017
)
2118

@@ -60,6 +57,10 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
6057
parsed_url = parse_url(str(request.url), sanitize=False)
6158

6259
if is_span_streaming_enabled:
60+
if sentry_sdk.traces.get_current_span() is None:
61+
propagate_trace_headers(client, request)
62+
return real_send(self, request, **kwargs)
63+
6364
with sentry_sdk.traces.start_span(
6465
name="%s %s"
6566
% (
@@ -81,21 +82,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
8182
if parsed_url.fragment:
8283
attributes["url.fragment"] = parsed_url.fragment
8384

84-
if should_propagate_trace(client, str(request.url)):
85-
for (
86-
key,
87-
value,
88-
) in (
89-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
90-
):
91-
logger.debug(
92-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
93-
)
94-
95-
if key == BAGGAGE_HEADER_NAME:
96-
add_sentry_baggage_to_headers(request.headers, value)
97-
else:
98-
request.headers[key] = value
85+
propagate_trace_headers(client, request)
9986

10087
try:
10188
rv = real_send(self, request, **kwargs)
@@ -125,21 +112,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
125112
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
126113
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
127114

128-
if should_propagate_trace(client, str(request.url)):
129-
for (
130-
key,
131-
value,
132-
) in (
133-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
134-
):
135-
logger.debug(
136-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
137-
)
138-
139-
if key == BAGGAGE_HEADER_NAME:
140-
add_sentry_baggage_to_headers(request.headers, value)
141-
else:
142-
request.headers[key] = value
115+
propagate_trace_headers(client, request)
143116

144117
rv = real_send(self, request, **kwargs)
145118

@@ -170,6 +143,10 @@ async def send(
170143
parsed_url = parse_url(str(request.url), sanitize=False)
171144

172145
if is_span_streaming_enabled:
146+
if sentry_sdk.traces.get_current_span() is None:
147+
propagate_trace_headers(client, request)
148+
return await real_send(self, request, **kwargs)
149+
173150
with sentry_sdk.traces.start_span(
174151
name="%s %s"
175152
% (
@@ -191,21 +168,7 @@ async def send(
191168
if parsed_url.fragment:
192169
attributes["url.fragment"] = parsed_url.fragment
193170

194-
if should_propagate_trace(client, str(request.url)):
195-
for (
196-
key,
197-
value,
198-
) in (
199-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
200-
):
201-
logger.debug(
202-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
203-
)
204-
205-
if key == BAGGAGE_HEADER_NAME:
206-
add_sentry_baggage_to_headers(request.headers, value)
207-
else:
208-
request.headers[key] = value
171+
propagate_trace_headers(client, request)
209172

210173
try:
211174
rv = await real_send(self, request, **kwargs)
@@ -235,20 +198,7 @@ async def send(
235198
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
236199
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
237200

238-
if should_propagate_trace(client, str(request.url)):
239-
for (
240-
key,
241-
value,
242-
) in (
243-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
244-
):
245-
logger.debug(
246-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
247-
)
248-
if key == BAGGAGE_HEADER_NAME:
249-
add_sentry_baggage_to_headers(request.headers, value)
250-
else:
251-
request.headers[key] = value
201+
propagate_trace_headers(client, request)
252202

253203
rv = await real_send(self, request, **kwargs)
254204

sentry_sdk/integrations/httpx2.py

Lines changed: 15 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,15 @@
44
from sentry_sdk.consts import OP, SPANDATA
55
from sentry_sdk.integrations import DidNotEnable, Integration
66
from sentry_sdk.scope import should_send_default_pii
7-
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME
87
from sentry_sdk.tracing_utils import (
98
add_http_request_source,
10-
add_sentry_baggage_to_headers,
119
has_span_streaming_enabled,
12-
should_propagate_trace,
10+
propagate_trace_headers,
1311
)
1412
from sentry_sdk.utils import (
1513
SENSITIVE_DATA_SUBSTITUTE,
1614
capture_internal_exceptions,
1715
ensure_integration_enabled,
18-
logger,
1916
parse_url,
2017
)
2118

@@ -60,6 +57,11 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
6057
parsed_url = parse_url(str(request.url), sanitize=False)
6158

6259
if is_span_streaming_enabled:
60+
if sentry_sdk.traces.get_current_span() is None:
61+
propagate_trace_headers(client, request)
62+
63+
return real_send(self, request, **kwargs)
64+
6365
with sentry_sdk.traces.start_span(
6466
name="%s %s"
6567
% (
@@ -81,21 +83,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
8183
if parsed_url.fragment:
8284
attributes["url.fragment"] = parsed_url.fragment
8385

84-
if should_propagate_trace(client, str(request.url)):
85-
for (
86-
key,
87-
value,
88-
) in (
89-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
90-
):
91-
logger.debug(
92-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
93-
)
94-
95-
if key == BAGGAGE_HEADER_NAME:
96-
add_sentry_baggage_to_headers(request.headers, value)
97-
else:
98-
request.headers[key] = value
86+
propagate_trace_headers(client, request)
9987

10088
try:
10189
rv = real_send(self, request, **kwargs)
@@ -125,21 +113,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
125113
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
126114
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
127115

128-
if should_propagate_trace(client, str(request.url)):
129-
for (
130-
key,
131-
value,
132-
) in (
133-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
134-
):
135-
logger.debug(
136-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
137-
)
138-
139-
if key == BAGGAGE_HEADER_NAME:
140-
add_sentry_baggage_to_headers(request.headers, value)
141-
else:
142-
request.headers[key] = value
116+
propagate_trace_headers(client, request)
143117

144118
rv = real_send(self, request, **kwargs)
145119

@@ -170,6 +144,11 @@ async def send(
170144
parsed_url = parse_url(str(request.url), sanitize=False)
171145

172146
if is_span_streaming_enabled:
147+
if sentry_sdk.traces.get_current_span() is None:
148+
propagate_trace_headers(client, request)
149+
150+
return await real_send(self, request, **kwargs)
151+
173152
with sentry_sdk.traces.start_span(
174153
name="%s %s"
175154
% (
@@ -191,21 +170,7 @@ async def send(
191170
if parsed_url.fragment:
192171
attributes["url.fragment"] = parsed_url.fragment
193172

194-
if should_propagate_trace(client, str(request.url)):
195-
for (
196-
key,
197-
value,
198-
) in (
199-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
200-
):
201-
logger.debug(
202-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
203-
)
204-
205-
if key == BAGGAGE_HEADER_NAME:
206-
add_sentry_baggage_to_headers(request.headers, value)
207-
else:
208-
request.headers[key] = value
173+
propagate_trace_headers(client, request)
209174

210175
try:
211176
rv = await real_send(self, request, **kwargs)
@@ -235,20 +200,7 @@ async def send(
235200
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
236201
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
237202

238-
if should_propagate_trace(client, str(request.url)):
239-
for (
240-
key,
241-
value,
242-
) in (
243-
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
244-
):
245-
logger.debug(
246-
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
247-
)
248-
if key == BAGGAGE_HEADER_NAME:
249-
add_sentry_baggage_to_headers(request.headers, value)
250-
else:
251-
request.headers[key] = value
203+
propagate_trace_headers(client, request)
252204

253205
rv = await real_send(self, request, **kwargs)
254206

sentry_sdk/integrations/pyreqwest.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
add_http_request_source,
1313
add_sentry_baggage_to_headers,
1414
has_span_streaming_enabled,
15+
propagate_trace_headers,
1516
should_propagate_trace,
1617
)
1718
from sentry_sdk.utils import (
@@ -88,6 +89,11 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]":
8889

8990
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
9091
if span_streaming:
92+
if sentry_sdk.traces.get_current_span() is None:
93+
propagate_trace_headers(client=sentry_sdk.get_client(), request=request)
94+
yield None
95+
return
96+
9197
with sentry_sdk.traces.start_span(
9298
name=f"{request.method} {parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE}",
9399
attributes={
@@ -101,26 +107,13 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]":
101107
span.set_attribute(SPANDATA.URL_QUERY, parsed_url.query)
102108
span.set_attribute(SPANDATA.URL_FRAGMENT, parsed_url.fragment)
103109

104-
if should_propagate_trace(sentry_sdk.get_client(), str(request.url)):
105-
for (
106-
key,
107-
value,
108-
) in sentry_sdk.get_current_scope().iter_trace_propagation_headers():
109-
logger.debug(
110-
"[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format(
111-
key=key, value=value, url=request.url
112-
)
113-
)
114-
115-
if key == BAGGAGE_HEADER_NAME:
116-
add_sentry_baggage_to_headers(request.headers, value)
117-
else:
118-
request.headers[key] = value
110+
propagate_trace_headers(client=sentry_sdk.get_client(), request=request)
119111

120112
yield span
121113

122-
with capture_internal_exceptions():
123-
add_http_request_source(span)
114+
if span is not None:
115+
with capture_internal_exceptions():
116+
add_http_request_source(span)
124117

125118
return
126119

@@ -171,7 +164,7 @@ async def sentry_async_middleware(
171164
SPANDATA.HTTP_STATUS_CODE,
172165
response.status,
173166
)
174-
else:
167+
elif span is not None:
175168
span.set_http_status(response.status)
176169

177170
return response
@@ -191,7 +184,7 @@ def sentry_sync_middleware(
191184
SPANDATA.HTTP_STATUS_CODE,
192185
response.status,
193186
)
194-
else:
187+
elif span is not None:
195188
span.set_http_status(response.status)
196189

197190
return response

0 commit comments

Comments
 (0)