Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
41192b7
fix(tracing): Skip child span creation in streaming path when no curr…
sentrivana Jul 13, 2026
fcade98
fix(tests): Update httpx2 and strawberry tests for streaming child sp…
sentrivana Jul 13, 2026
6ea870f
test(httpx): Wrap streaming tests in parent span
ericapisani Jul 14, 2026
ef2232f
Merge branch 'master' into ivana/streaming-child-spans-http-clients
ericapisani Jul 15, 2026
877be03
fix(tracing): Propagate trace headers in streaming path when no curre…
ericapisani Jul 15, 2026
c8c9924
fix(tracing): Propagate trace headers for httpx when no current span
ericapisani Jul 15, 2026
655bd41
fix(tracing): Skip child span creation in streaming path when no curr…
sentrivana Jul 13, 2026
a8cf0f4
fix(tests): Update httpx2 and strawberry tests for streaming child sp…
sentrivana Jul 13, 2026
1e9a5d7
test(httpx): Wrap streaming tests in parent span
ericapisani Jul 14, 2026
aeb7bd8
fix(tracing): Propagate trace headers in streaming path when no curre…
ericapisani Jul 15, 2026
2b59942
fix(tracing): Propagate trace headers for httpx when no current span
ericapisani Jul 15, 2026
4afedf1
neel fixes
sl0thentr0py Jul 16, 2026
a6f5f5a
update pyreqwest to use trace propagation helper method
ericapisani Jul 16, 2026
557ecc0
Merge branch 'ivana/streaming-child-spans-http-clients' of github.com…
ericapisani Jul 16, 2026
ac29368
add trace propagation
ericapisani Jul 16, 2026
94e5b1f
bad merge
ericapisani Jul 16, 2026
df1a62f
Merge remote-tracking branch 'origin/master' into ivana/streaming-chi…
sl0thentr0py Jul 16, 2026
a2f1bd3
remove nullcontext
sl0thentr0py Jul 16, 2026
94a8d62
fix arq tests
sl0thentr0py Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/boto3.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def _sentry_request_created(
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
span: "Union[Span, StreamedSpan]"
if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
return
span = sentry_sdk.traces.start_span(
name=description,
attributes={
Expand Down
76 changes: 13 additions & 63 deletions sentry_sdk/integrations/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME
from sentry_sdk.tracing_utils import (
add_http_request_source,
add_sentry_baggage_to_headers,
has_span_streaming_enabled,
should_propagate_trace,
propagate_trace_headers,
)
from sentry_sdk.utils import (
SENSITIVE_DATA_SUBSTITUTE,
capture_internal_exceptions,
ensure_integration_enabled,
logger,
parse_url,
)

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

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
propagate_trace_headers(client, request)
Comment on lines +60 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The httpx integration fails to propagate trace headers when no active span exists and streaming is enabled, unlike the httpx2 integration.
Severity: MEDIUM

Suggested Fix

Add a call to propagate_trace_headers(client, request) before the early-return statements in the synchronous and asynchronous send methods in sentry_sdk/integrations/httpx.py. This will make its behavior consistent with the httpx2.py implementation.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: sentry_sdk/integrations/httpx.py#L60-L61

Potential issue: In `sentry_sdk/integrations/httpx.py`, both the synchronous `send` and
asynchronous `send` methods do not propagate trace headers when returning early if no
active span exists and span streaming is enabled. Specifically,
`propagate_trace_headers(client, request)` is not called before the early return. This
behavior is inconsistent with the `httpx2.py` integration, which handles this case
correctly. The consequence is that distributed tracing is broken for `httpx` requests
made outside of an active transaction when streaming is used.

Also affects:

  • sentry_sdk/integrations/httpx.py:145~146

Did we get this right? 👍 / 👎 to inform future reviews.

return real_send(self, request, **kwargs)
Comment thread
cursor[bot] marked this conversation as resolved.

with sentry_sdk.traces.start_span(
name="%s %s"
% (
Expand All @@ -81,21 +82,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)

if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)

if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

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

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
propagate_trace_headers(client, request)
return await real_send(self, request, **kwargs)

Comment thread
sentry-warden[bot] marked this conversation as resolved.
with sentry_sdk.traces.start_span(
name="%s %s"
% (
Expand All @@ -191,21 +168,7 @@ async def send(
if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)

if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)
if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

Expand Down
78 changes: 15 additions & 63 deletions sentry_sdk/integrations/httpx2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME
from sentry_sdk.tracing_utils import (
add_http_request_source,
add_sentry_baggage_to_headers,
has_span_streaming_enabled,
should_propagate_trace,
propagate_trace_headers,
)
from sentry_sdk.utils import (
SENSITIVE_DATA_SUBSTITUTE,
capture_internal_exceptions,
ensure_integration_enabled,
logger,
parse_url,
)

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

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
propagate_trace_headers(client, request)

return real_send(self, request, **kwargs)
Comment thread
sentry-warden[bot] marked this conversation as resolved.

with sentry_sdk.traces.start_span(
name="%s %s"
% (
Expand All @@ -81,21 +83,7 @@ def send(self: "Client", request: "Request", **kwargs: "Any") -> "Response":
if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)

if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)

if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

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

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
propagate_trace_headers(client, request)

return await real_send(self, request, **kwargs)

with sentry_sdk.traces.start_span(
name="%s %s"
% (
Expand All @@ -191,21 +170,7 @@ async def send(
if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)

if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

if should_propagate_trace(client, str(request.url)):
for (
key,
value,
) in (
sentry_sdk.get_current_scope().iter_trace_propagation_headers()
):
logger.debug(
f"[Tracing] Adding `{key}` header {value} to outgoing request to {request.url}."
)
if key == BAGGAGE_HEADER_NAME:
add_sentry_baggage_to_headers(request.headers, value)
else:
request.headers[key] = value
propagate_trace_headers(client, request)

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

Expand Down
31 changes: 12 additions & 19 deletions sentry_sdk/integrations/pyreqwest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
add_http_request_source,
add_sentry_baggage_to_headers,
has_span_streaming_enabled,
propagate_trace_headers,
should_propagate_trace,
)
from sentry_sdk.utils import (
Expand Down Expand Up @@ -88,6 +89,11 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]":

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
Comment thread
cursor[bot] marked this conversation as resolved.

with sentry_sdk.traces.start_span(
name=f"{request.method} {parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE}",
attributes={
Expand All @@ -101,26 +107,13 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]":
span.set_attribute(SPANDATA.URL_QUERY, parsed_url.query)
span.set_attribute(SPANDATA.URL_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
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)

return

Expand Down Expand Up @@ -171,7 +164,7 @@ async def sentry_async_middleware(
SPANDATA.HTTP_STATUS_CODE,
response.status,
)
else:
elif span is not None:
span.set_http_status(response.status)

return response
Expand All @@ -191,7 +184,7 @@ def sentry_sync_middleware(
SPANDATA.HTTP_STATUS_CODE,
response.status,
)
else:
elif span is not None:
span.set_http_status(response.status)

return response
Loading
Loading