Skip to content

Commit ebcf1e2

Browse files
sentrivanaclaude
andcommitted
fix(tracing): Skip child span creation in streaming path when no current span (misc)
When span streaming is enabled and there is no current span, integrations should not create new root segments for child-span operations. This adds a guard check using `sentry_sdk.traces.get_current_span()` before creating spans in the streaming path. Affected integrations: socket, grpc (client), asyncio, mcp, rust_tracing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2b9c102 commit ebcf1e2

6 files changed

Lines changed: 93 additions & 50 deletions

File tree

sentry_sdk/integrations/asyncio.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,14 @@ async def _task_with_sentry_span_creation() -> "Any":
155155
with sentry_sdk.isolation_scope():
156156
if task_spans:
157157
if is_span_streaming_enabled:
158-
span_ctx = sentry_sdk.traces.start_span(
159-
name=get_name(coro),
160-
attributes={
161-
"sentry.op": OP.FUNCTION,
162-
"sentry.origin": AsyncioIntegration.origin,
163-
},
164-
)
158+
if sentry_sdk.traces.get_current_span() is not None:
159+
span_ctx = sentry_sdk.traces.start_span(
160+
name=get_name(coro),
161+
attributes={
162+
"sentry.op": OP.FUNCTION,
163+
"sentry.origin": AsyncioIntegration.origin,
164+
},
165+
)
165166
else:
166167
span_ctx = sentry_sdk.start_span(
167168
op=OP.FUNCTION,

sentry_sdk/integrations/grpc/aio/client.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ async def intercept_unary_unary(
5252

5353
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
5454
if span_streaming:
55+
if sentry_sdk.traces.get_current_span() is None:
56+
client_call_details = (
57+
self._update_client_call_details_metadata_from_scope(
58+
client_call_details
59+
)
60+
)
61+
return await continuation(client_call_details, request)
5562
with sentry_sdk.traces.start_span(
5663
name="unary unary call to %s" % method.decode(),
5764
attributes={
@@ -107,6 +114,13 @@ async def intercept_unary_stream(
107114

108115
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
109116
if span_streaming:
117+
if sentry_sdk.traces.get_current_span() is None:
118+
client_call_details = (
119+
self._update_client_call_details_metadata_from_scope(
120+
client_call_details
121+
)
122+
)
123+
return await continuation(client_call_details, request)
110124
with sentry_sdk.traces.start_span(
111125
name="unary stream call to %s" % method.decode(),
112126
attributes={

sentry_sdk/integrations/grpc/client.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ def intercept_unary_unary(
3333

3434
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
3535
if span_streaming:
36+
if sentry_sdk.traces.get_current_span() is None:
37+
client_call_details = (
38+
self._update_client_call_details_metadata_from_scope(
39+
client_call_details
40+
)
41+
)
42+
return continuation(client_call_details, request)
3643
with sentry_sdk.traces.start_span(
3744
name="unary unary call to %s" % method,
3845
attributes={
@@ -84,6 +91,13 @@ def intercept_unary_stream(
8491
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
8592
response: "UnaryStreamCall"
8693
if span_streaming:
94+
if sentry_sdk.traces.get_current_span() is None:
95+
client_call_details = (
96+
self._update_client_call_details_metadata_from_scope(
97+
client_call_details
98+
)
99+
)
100+
return continuation(client_call_details, request)
87101
with sentry_sdk.traces.start_span(
88102
name="unary stream call to %s" % method,
89103
attributes={

sentry_sdk/integrations/mcp.py

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -567,15 +567,18 @@ async def _handler_wrapper(
567567
# Start span and execute
568568
with isolation_scope_context:
569569
with current_scope_context:
570-
span_mgr: "Union[Span, StreamedSpan]"
570+
span_mgr: "Union[Span, StreamedSpan, ContextManager[None]]"
571571
if span_streaming:
572-
span_mgr = sentry_sdk.traces.start_span(
573-
name=span_name,
574-
attributes={
575-
"sentry.op": OP.MCP_SERVER,
576-
"sentry.origin": MCPIntegration.origin,
577-
},
578-
)
572+
if sentry_sdk.traces.get_current_span() is not None:
573+
span_mgr = sentry_sdk.traces.start_span(
574+
name=span_name,
575+
attributes={
576+
"sentry.op": OP.MCP_SERVER,
577+
"sentry.origin": MCPIntegration.origin,
578+
},
579+
)
580+
else:
581+
span_mgr = nullcontext()
579582
else:
580583
span_mgr = get_start_span_function()(
581584
op=OP.MCP_SERVER,
@@ -584,40 +587,41 @@ async def _handler_wrapper(
584587
)
585588

586589
with span_mgr as span:
587-
# Set input span data
588-
_set_span_input_data(
589-
span,
590-
handler_name,
591-
span_data_key,
592-
mcp_method_name,
593-
arguments,
594-
request_id,
595-
session_id,
596-
mcp_transport,
597-
)
590+
if span is not None:
591+
# Set input span data
592+
_set_span_input_data(
593+
span,
594+
handler_name,
595+
span_data_key,
596+
mcp_method_name,
597+
arguments,
598+
request_id,
599+
session_id,
600+
mcp_transport,
601+
)
598602

599-
# For resources, extract and set protocol
600-
if handler_type == "resource":
601-
uri = None
602-
if params is not None:
603-
uri = getattr(params, "uri", None)
604-
605-
# v1 scenario
606-
if ServerRequestContext is None:
607-
if original_args:
608-
uri = original_args[0]
609-
else:
610-
uri = original_kwargs.get("uri")
611-
612-
protocol = None
613-
if uri is not None and hasattr(uri, "scheme"):
614-
protocol = uri.scheme
615-
elif handler_name and "://" in handler_name:
616-
protocol = handler_name.split("://")[0]
617-
if protocol:
618-
_set_span_data_attribute(
619-
span, SPANDATA.MCP_RESOURCE_PROTOCOL, protocol
620-
)
603+
# For resources, extract and set protocol
604+
if handler_type == "resource":
605+
uri = None
606+
if params is not None:
607+
uri = getattr(params, "uri", None)
608+
609+
# v1 scenario
610+
if ServerRequestContext is None:
611+
if original_args:
612+
uri = original_args[0]
613+
else:
614+
uri = original_kwargs.get("uri")
615+
616+
protocol = None
617+
if uri is not None and hasattr(uri, "scheme"):
618+
protocol = uri.scheme
619+
elif handler_name and "://" in handler_name:
620+
protocol = handler_name.split("://")[0]
621+
if protocol:
622+
_set_span_data_attribute(
623+
span, SPANDATA.MCP_RESOURCE_PROTOCOL, protocol
624+
)
621625

622626
try:
623627
# Execute the async handler
@@ -630,14 +634,15 @@ async def _handler_wrapper(
630634

631635
except Exception as e:
632636
# Set error flag for tools
633-
if handler_type == "tool":
637+
if span is not None and handler_type == "tool":
634638
_set_span_data_attribute(
635639
span, SPANDATA.MCP_TOOL_RESULT_IS_ERROR, True
636640
)
637641
sentry_sdk.capture_exception(e)
638642
raise
639643

640-
_set_span_output_data(span, result, result_data_key, handler_type)
644+
if span is not None:
645+
_set_span_output_data(span, result, result_data_key, handler_type)
641646

642647
return result
643648

sentry_sdk/integrations/rust_tracing.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ def on_new_span(
208208

209209
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
210210
if span_streaming:
211+
if sentry_sdk.traces.get_current_span() is None:
212+
return None
213+
211214
sentry_span = sentry_sdk.traces.start_span(
212215
name=sentry_span_name,
213216
attributes={

sentry_sdk/integrations/socket.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def create_connection(
5757
return real_create_connection(address, timeout, source_address)
5858

5959
if has_span_streaming_enabled(client.options):
60+
if sentry_sdk.traces.get_current_span() is None:
61+
return real_create_connection(address, timeout, source_address)
62+
6063
with sentry_sdk.traces.start_span(
6164
name=_get_span_description(address[0], address[1]),
6265
attributes={
@@ -105,6 +108,9 @@ def getaddrinfo(
105108
return real_getaddrinfo(host, port, family, type, proto, flags)
106109

107110
if has_span_streaming_enabled(client.options):
111+
if sentry_sdk.traces.get_current_span() is None:
112+
return real_getaddrinfo(host, port, family, type, proto, flags)
113+
108114
with sentry_sdk.traces.start_span(
109115
name=_get_span_description(host, port),
110116
attributes={

0 commit comments

Comments
 (0)