Skip to content

Commit 557ecc0

Browse files
committed
Merge branch 'ivana/streaming-child-spans-http-clients' of github.com:getsentry/sentry-python into ivana/streaming-child-spans-http-clients
2 parents a6f5f5a + 4afedf1 commit 557ecc0

10 files changed

Lines changed: 116 additions & 71 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/pyreqwest.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,23 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]":
9090

9191
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
9292
if span_streaming:
93-
span_ctx = nullcontext()
94-
if sentry_sdk.traces.get_current_span() is not None:
95-
span_ctx = sentry_sdk.traces.start_span(
96-
name=f"{request.method} {parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE}",
97-
attributes={
98-
"sentry.op": OP.HTTP_CLIENT,
99-
"sentry.origin": PyreqwestIntegration.origin,
100-
SPANDATA.HTTP_REQUEST_METHOD: request.method,
101-
},
102-
)
103-
with span_ctx as span:
104-
if span is not None:
105-
if parsed_url is not None and should_send_default_pii():
106-
span.set_attribute(SPANDATA.URL_FULL, parsed_url.url)
107-
span.set_attribute(SPANDATA.URL_QUERY, parsed_url.query)
108-
span.set_attribute(SPANDATA.URL_FRAGMENT, parsed_url.fragment)
109-
93+
if sentry_sdk.traces.get_current_span() is None:
11094
propagate_trace_headers(client=sentry_sdk.get_client(), request=request)
95+
yield None
96+
return
97+
98+
with sentry_sdk.traces.start_span(
99+
name=f"{request.method} {parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE}",
100+
attributes={
101+
"sentry.op": OP.HTTP_CLIENT,
102+
"sentry.origin": PyreqwestIntegration.origin,
103+
SPANDATA.HTTP_REQUEST_METHOD: request.method,
104+
},
105+
) as span:
106+
if parsed_url is not None and should_send_default_pii():
107+
span.set_attribute(SPANDATA.URL_FULL, parsed_url.url)
108+
span.set_attribute(SPANDATA.URL_QUERY, parsed_url.query)
109+
span.set_attribute(SPANDATA.URL_FRAGMENT, parsed_url.fragment)
111110

112111
yield span
113112

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={

sentry_sdk/tracing_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,9 +985,12 @@ def propagate_trace_headers(
985985
(e.g. an ``httpx``/``httpx2`` ``Request``).
986986
"""
987987
if not hasattr(request, "url") or not hasattr(request, "headers"):
988+
<<<<<<< HEAD
988989
logger.warning(
989990
"Unable to propagate trace headers in request - missing url or headers attributes"
990991
)
992+
=======
993+
>>>>>>> 4afedf112f0ab2e8879a4bb09642329e48ee2f9c
991994
return
992995

993996
if not should_propagate_trace(client, str(request.url)):

tests/integrations/grpc/test_grpc_aio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ async def test_noop_for_unimplemented_method(
100100

101101
sentry_sdk.flush()
102102
spans = [item.payload for item in items]
103-
assert len(spans) == 1 # Only client span present.
103+
assert len(spans) == 0 # No client span created without an active span.
104104
else:
105105
events = capture_events()
106106

tests/integrations/rust_tracing/test_rust_tracing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,10 @@ def test_on_new_span_without_transaction(sentry_init, span_streaming):
309309
if span_streaming:
310310
assert sentry_sdk.traces.get_current_span() is None
311311

312+
# In streaming mode we do not create an orphan root segment when there
313+
# is no active span
312314
rust_tracing.new_span(RustTracingLevel.Info, 3)
313-
current_span = sentry_sdk.traces.get_current_span()
314-
assert current_span is not None
315-
assert current_span._segment is current_span
315+
assert sentry_sdk.traces.get_current_span() is None
316316
else:
317317
assert sentry_sdk.get_current_span() is None
318318

0 commit comments

Comments
 (0)