diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index d22f3a745b..f262f1330f 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -351,8 +351,12 @@ async def on_request_start( parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, ) - span: "Union[Span, StreamedSpan]" + span: "Union[Span, StreamedSpan, None]" if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + trace_config_ctx.span = None + return + attributes: "Attributes" = { "sentry.op": OP.HTTP_CLIENT, "sentry.origin": AioHttpIntegration.origin, diff --git a/sentry_sdk/integrations/aiomysql.py b/sentry_sdk/integrations/aiomysql.py index 49459268e6..c3348566a2 100644 --- a/sentry_sdk/integrations/aiomysql.py +++ b/sentry_sdk/integrations/aiomysql.py @@ -179,33 +179,34 @@ async def _inner(self: "Connection") -> T: "sentry.origin": AioMySQLIntegration.origin, } | breadcrumb_data + if sentry_sdk.traces.get_current_span() is None: + return await f(self) + with sentry_sdk.traces.start_span( name="connect", attributes=span_attributes - ) as span: + ): with capture_internal_exceptions(): sentry_sdk.add_breadcrumb( message="connect", category="query", data=breadcrumb_data ) - res = await f(self) - else: - connect_data = _get_connect_data(self) - - with sentry_sdk.start_span( - op=OP.DB, - name="connect", - origin=AioMySQLIntegration.origin, - ) as span: - _set_db_data(span, self) + return await f(self) - with capture_internal_exceptions(): - sentry_sdk.add_breadcrumb( - message="connect", - category="query", - data=connect_data, - ) - res = await f(self) + connect_data = _get_connect_data(self) - return res + with sentry_sdk.start_span( + op=OP.DB, + name="connect", + origin=AioMySQLIntegration.origin, + ) as span: + _set_db_data(span, self) + + with capture_internal_exceptions(): + sentry_sdk.add_breadcrumb( + message="connect", + category="query", + data=connect_data, + ) + return await f(self) return _inner diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index dfa4aef34c..e9dddb4db4 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -640,6 +640,8 @@ def _sentry_patched_create_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) span = sentry_sdk.traces.start_span( name=f"chat {model}".strip(), attributes={ @@ -738,6 +740,8 @@ async def _sentry_patched_create_async( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return await f(*args, **kwargs) span = sentry_sdk.traces.start_span( name=f"chat {model}".strip(), attributes={ @@ -982,6 +986,8 @@ def _sentry_patched_enter(self: "MessageStreamManager") -> "MessageStream": return f(self) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(self) span = sentry_sdk.traces.start_span( name="chat" if self._model is None else f"chat {self._model}".strip(), attributes={ @@ -1089,6 +1095,8 @@ async def _sentry_patched_aenter( return await f(self) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(self) span = sentry_sdk.traces.start_span( name="chat" if self._model is None else f"chat {self._model}".strip(), attributes={ diff --git a/sentry_sdk/integrations/arq.py b/sentry_sdk/integrations/arq.py index 3488457e20..011c5e51bf 100644 --- a/sentry_sdk/integrations/arq.py +++ b/sentry_sdk/integrations/arq.py @@ -79,6 +79,9 @@ async def _sentry_enqueue_job( return await old_enqueue_job(self, function, *args, **kwargs) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await old_enqueue_job(self, function, *args, **kwargs) + with sentry_sdk.traces.start_span( name=function, attributes={ diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index bf30a69e67..4fc600d36f 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -155,13 +155,14 @@ async def _task_with_sentry_span_creation() -> "Any": with sentry_sdk.isolation_scope(): if task_spans: if is_span_streaming_enabled: - span_ctx = sentry_sdk.traces.start_span( - name=get_name(coro), - attributes={ - "sentry.op": OP.FUNCTION, - "sentry.origin": AsyncioIntegration.origin, - }, - ) + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=get_name(coro), + attributes={ + "sentry.op": OP.FUNCTION, + "sentry.origin": AsyncioIntegration.origin, + }, + ) else: span_ctx = sentry_sdk.start_span( op=OP.FUNCTION, diff --git a/sentry_sdk/integrations/asyncpg.py b/sentry_sdk/integrations/asyncpg.py index 186176d268..f79d1507d8 100644 --- a/sentry_sdk/integrations/asyncpg.py +++ b/sentry_sdk/integrations/asyncpg.py @@ -235,39 +235,39 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T": except IndexError: pass + if sentry_sdk.traces.get_current_span() is None: + return await f(*args, **kwargs) + with sentry_sdk.traces.start_span( name="connect", attributes=span_attributes - ) as span: + ): with capture_internal_exceptions(): sentry_sdk.add_breadcrumb( message="connect", category="query", data=span_attributes ) - res = await f(*args, **kwargs) - - else: - with sentry_sdk.start_span( - op=OP.DB, - name="connect", - origin=AsyncPGIntegration.origin, - ) as span: - span.set_data(SPANDATA.DB_SYSTEM, "postgresql") - if addr: - try: - span.set_data(SPANDATA.SERVER_ADDRESS, addr[0]) - span.set_data(SPANDATA.SERVER_PORT, addr[1]) - except IndexError: - pass - span.set_data(SPANDATA.DB_NAME, database) - span.set_data(SPANDATA.DB_USER, user) - span.set_data(SPANDATA.DB_DRIVER_NAME, "asyncpg") + return await f(*args, **kwargs) - with capture_internal_exceptions(): - sentry_sdk.add_breadcrumb( - message="connect", category="query", data=span._data - ) - res = await f(*args, **kwargs) + with sentry_sdk.start_span( + op=OP.DB, + name="connect", + origin=AsyncPGIntegration.origin, + ) as span: + span.set_data(SPANDATA.DB_SYSTEM, "postgresql") + if addr: + try: + span.set_data(SPANDATA.SERVER_ADDRESS, addr[0]) + span.set_data(SPANDATA.SERVER_PORT, addr[1]) + except IndexError: + pass + span.set_data(SPANDATA.DB_NAME, database) + span.set_data(SPANDATA.DB_USER, user) + span.set_data(SPANDATA.DB_DRIVER_NAME, "asyncpg") - return res + with capture_internal_exceptions(): + sentry_sdk.add_breadcrumb( + message="connect", category="query", data=span._data + ) + return await f(*args, **kwargs) return _inner diff --git a/sentry_sdk/integrations/boto3.py b/sentry_sdk/integrations/boto3.py index a7fdd99b21..69deefc7b7 100644 --- a/sentry_sdk/integrations/boto3.py +++ b/sentry_sdk/integrations/boto3.py @@ -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={ diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index 532b13539b..a4abac4618 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -419,6 +419,9 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": span_streaming = has_span_streaming_enabled(client.options) try: + if span_streaming and get_current_span() is None: + return f(*args, **kwargs) + span: "Union[Span, StreamedSpan]" if span_streaming: span = sentry_sdk.traces.start_span( @@ -462,7 +465,8 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": with capture_internal_exceptions(): set_on_span( - SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, task.request.retries + SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, + task.request.retries, ) with capture_internal_exceptions(): diff --git a/sentry_sdk/integrations/clickhouse_driver.py b/sentry_sdk/integrations/clickhouse_driver.py index e6b3009548..9583b25ca9 100644 --- a/sentry_sdk/integrations/clickhouse_driver.py +++ b/sentry_sdk/integrations/clickhouse_driver.py @@ -85,14 +85,16 @@ def _inner(*args: "P.args", **kwargs: "P.kwargs") -> "T": params = args[3] if len(args) > 3 else kwargs.get("params") if has_span_streaming_enabled(client.options): - span = sentry_sdk.traces.start_span( - name=query, # type: ignore - attributes={ - "sentry.op": OP.DB, - "sentry.origin": ClickhouseDriverIntegration.origin, - SPANDATA.DB_QUERY_TEXT: str(query), - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=query, # type: ignore + attributes={ + "sentry.op": OP.DB, + "sentry.origin": ClickhouseDriverIntegration.origin, + SPANDATA.DB_QUERY_TEXT: str(query), + }, + ) else: span = sentry_sdk.start_span( op=OP.DB, @@ -110,7 +112,8 @@ def _inner(*args: "P.args", **kwargs: "P.kwargs") -> "T": connection._sentry_span = span # type: ignore[attr-defined] - _set_db_data(span, connection) + if span is not None: + _set_db_data(span, connection) # run the original code ret = f(*args, **kwargs) diff --git a/sentry_sdk/integrations/cohere.py b/sentry_sdk/integrations/cohere.py index 7abf3f6808..c805c601e3 100644 --- a/sentry_sdk/integrations/cohere.py +++ b/sentry_sdk/integrations/cohere.py @@ -17,7 +17,11 @@ import sentry_sdk from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.utils import capture_internal_exceptions, event_from_exception, reraise +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, + reraise, +) try: from cohere import ( @@ -154,6 +158,8 @@ def new_chat(*args: "Any", **kwargs: "Any") -> "Any": message = kwargs.get("message") if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) span = sentry_sdk.traces.start_span( name="cohere.client.Chat", attributes={ @@ -250,6 +256,8 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": ) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) span_ctx = sentry_sdk.traces.start_span( name="Cohere Embedding Creation", attributes={ diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index 361b60079d..2c561164e6 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -724,6 +724,8 @@ def connect(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return real_connect(self) with sentry_sdk.traces.start_span( name="connect", attributes={ @@ -750,6 +752,8 @@ def _commit(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return real_commit(self) with sentry_sdk.traces.start_span( name=SPANNAME.DB_COMMIT, attributes={ @@ -776,6 +780,8 @@ def _rollback(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return real_rollback(self) with sentry_sdk.traces.start_span( name=SPANNAME.DB_ROLLBACK, attributes={ diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 43faffb5be..785fa2af34 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -191,6 +191,8 @@ async def sentry_wrapped_callback( return await callback(request, *args, **kwargs) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return await callback(request, *args, **kwargs) with sentry_sdk.traces.start_span( name=request.resolver_match.view_name, attributes={ diff --git a/sentry_sdk/integrations/django/caching.py b/sentry_sdk/integrations/django/caching.py index faf1803c11..2cfc0cd2e1 100644 --- a/sentry_sdk/integrations/django/caching.py +++ b/sentry_sdk/integrations/django/caching.py @@ -61,6 +61,8 @@ def _instrument_call( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return original_method(*args, **kwargs) with sentry_sdk.traces.start_span( name=description, attributes={ diff --git a/sentry_sdk/integrations/django/middleware.py b/sentry_sdk/integrations/django/middleware.py index a14ec96ff5..01ed8962e0 100644 --- a/sentry_sdk/integrations/django/middleware.py +++ b/sentry_sdk/integrations/django/middleware.py @@ -84,6 +84,8 @@ def _check_middleware_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) middleware_span: "Union[Span, StreamedSpan]" if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return None middleware_span = sentry_sdk.traces.start_span( name=description, attributes={ diff --git a/sentry_sdk/integrations/django/signals_handlers.py b/sentry_sdk/integrations/django/signals_handlers.py index 7140ead782..711e74b441 100644 --- a/sentry_sdk/integrations/django/signals_handlers.py +++ b/sentry_sdk/integrations/django/signals_handlers.py @@ -68,6 +68,8 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any": sentry_sdk.get_client().options ) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return receiver(*args, **kwargs) with sentry_sdk.traces.start_span( name=signal_name, attributes={ @@ -75,7 +77,7 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any": "sentry.origin": DjangoIntegration.origin, SPANDATA.CODE_FUNCTION_NAME: signal_name, }, - ) as span: + ): return receiver(*args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/tasks.py b/sentry_sdk/integrations/django/tasks.py index 5e23c258fb..303040d042 100644 --- a/sentry_sdk/integrations/django/tasks.py +++ b/sentry_sdk/integrations/django/tasks.py @@ -35,6 +35,8 @@ def _sentry_enqueue(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return old_task_enqueue(self, *args, **kwargs) with sentry_sdk.traces.start_span( name=name, attributes={ diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index 5ab89d4a74..1c0986dd9e 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -64,13 +64,15 @@ def patch_templates() -> None: def rendered_content(self: "SimpleTemplateResponse") -> str: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return real_rendered_content.fget(self) with sentry_sdk.traces.start_span( name=_get_template_name_description(self.template_name), attributes={ "sentry.op": OP.TEMPLATE_RENDER, "sentry.origin": DjangoIntegration.origin, }, - ) as span: + ): return real_rendered_content.fget(self) else: with sentry_sdk.start_span( @@ -109,13 +111,15 @@ def render( span_streaming = has_span_streaming_enabled(client.options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return real_render(request, template_name, context, *args, **kwargs) with sentry_sdk.traces.start_span( name=_get_template_name_description(template_name), attributes={ "sentry.op": OP.TEMPLATE_RENDER, "sentry.origin": DjangoIntegration.origin, }, - ) as span: + ): return real_render(request, template_name, context, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/views.py b/sentry_sdk/integrations/django/views.py index cf3012a75e..b24e4df45b 100644 --- a/sentry_sdk/integrations/django/views.py +++ b/sentry_sdk/integrations/django/views.py @@ -34,6 +34,8 @@ def patch_views() -> None: def sentry_patched_render(self: "SimpleTemplateResponse") -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return old_render(self) with sentry_sdk.traces.start_span( name="serialize response", attributes={ @@ -108,6 +110,8 @@ def sentry_wrapped_callback(request: "Any", *args: "Any", **kwargs: "Any") -> "A return callback(request, *args, **kwargs) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return callback(request, *args, **kwargs) with sentry_sdk.traces.start_span( name=request.resolver_match.view_name, attributes={ diff --git a/sentry_sdk/integrations/google_genai/__init__.py b/sentry_sdk/integrations/google_genai/__init__.py index 45652c3f71..ed46553d81 100644 --- a/sentry_sdk/integrations/google_genai/__init__.py +++ b/sentry_sdk/integrations/google_genai/__init__.py @@ -76,17 +76,19 @@ def new_generate_content_stream( _model, contents, model_name = prepare_generate_content_args(args, kwargs) if has_span_streaming_enabled(client.options): - chat_span = sentry_sdk.traces.start_span( - name=f"chat {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_CHAT, - "sentry.origin": ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "chat", - SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, - SPANDATA.GEN_AI_REQUEST_MODEL: model_name, - SPANDATA.GEN_AI_RESPONSE_STREAMING: True, - }, - ) + chat_span = None + if sentry_sdk.traces.get_current_span() is not None: + chat_span = sentry_sdk.traces.start_span( + name=f"chat {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_CHAT, + "sentry.origin": ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "chat", + SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, + SPANDATA.GEN_AI_REQUEST_MODEL: model_name, + SPANDATA.GEN_AI_RESPONSE_STREAMING: True, + }, + ) else: chat_span = get_start_span_function()( op=OP.GEN_AI_CHAT, @@ -100,7 +102,10 @@ def new_generate_content_stream( chat_span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) chat_span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) - set_span_data_for_request(chat_span, integration, model_name, contents, kwargs) + if chat_span is not None: + set_span_data_for_request( + chat_span, integration, model_name, contents, kwargs + ) try: stream = f(self, *args, **kwargs) @@ -114,25 +119,28 @@ def new_iterator() -> "Iterator[Any]": yield chunk except Exception as exc: _capture_exception(exc) - if isinstance(chat_span, StreamedSpan): - chat_span.status = SpanStatus.ERROR - else: - chat_span.set_status(SPANSTATUS.INTERNAL_ERROR) + if chat_span is not None: + if isinstance(chat_span, StreamedSpan): + chat_span.status = SpanStatus.ERROR + else: + chat_span.set_status(SPANSTATUS.INTERNAL_ERROR) raise finally: - # Accumulate all chunks and set final response data on spans - if chunks: - accumulated_response = accumulate_streaming_response(chunks) - set_span_data_for_streaming_response( - chat_span, integration, accumulated_response - ) - chat_span.__exit__(None, None, None) + if chat_span is not None: + # Accumulate all chunks and set final response data on spans + if chunks: + accumulated_response = accumulate_streaming_response(chunks) + set_span_data_for_streaming_response( + chat_span, integration, accumulated_response + ) + chat_span.__exit__(None, None, None) return new_iterator() except Exception as exc: _capture_exception(exc) - chat_span.__exit__(None, None, None) + if chat_span is not None: + chat_span.__exit__(None, None, None) raise return new_generate_content_stream @@ -153,17 +161,19 @@ async def new_async_generate_content_stream( _model, contents, model_name = prepare_generate_content_args(args, kwargs) if has_span_streaming_enabled(client.options): - chat_span = sentry_sdk.traces.start_span( - name=f"chat {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_CHAT, - "sentry.origin": ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "chat", - SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, - SPANDATA.GEN_AI_REQUEST_MODEL: model_name, - SPANDATA.GEN_AI_RESPONSE_STREAMING: True, - }, - ) + chat_span = None + if sentry_sdk.traces.get_current_span() is not None: + chat_span = sentry_sdk.traces.start_span( + name=f"chat {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_CHAT, + "sentry.origin": ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "chat", + SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, + SPANDATA.GEN_AI_REQUEST_MODEL: model_name, + SPANDATA.GEN_AI_RESPONSE_STREAMING: True, + }, + ) else: chat_span = get_start_span_function()( op=OP.GEN_AI_CHAT, @@ -177,7 +187,10 @@ async def new_async_generate_content_stream( chat_span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) chat_span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) - set_span_data_for_request(chat_span, integration, model_name, contents, kwargs) + if chat_span is not None: + set_span_data_for_request( + chat_span, integration, model_name, contents, kwargs + ) try: stream = await f(self, *args, **kwargs) @@ -191,25 +204,28 @@ async def new_async_iterator() -> "AsyncIterator[Any]": yield chunk except Exception as exc: _capture_exception(exc) - if isinstance(chat_span, StreamedSpan): - chat_span.status = SpanStatus.ERROR - else: - chat_span.set_status(SPANSTATUS.INTERNAL_ERROR) + if chat_span is not None: + if isinstance(chat_span, StreamedSpan): + chat_span.status = SpanStatus.ERROR + else: + chat_span.set_status(SPANSTATUS.INTERNAL_ERROR) raise finally: - # Accumulate all chunks and set final response data on spans - if chunks: - accumulated_response = accumulate_streaming_response(chunks) - set_span_data_for_streaming_response( - chat_span, integration, accumulated_response - ) - chat_span.__exit__(None, None, None) + if chat_span is not None: + # Accumulate all chunks and set final response data on spans + if chunks: + accumulated_response = accumulate_streaming_response(chunks) + set_span_data_for_streaming_response( + chat_span, integration, accumulated_response + ) + chat_span.__exit__(None, None, None) return new_async_iterator() except Exception as exc: _capture_exception(exc) - chat_span.__exit__(None, None, None) + if chat_span is not None: + chat_span.__exit__(None, None, None) raise return new_async_generate_content_stream @@ -226,6 +242,9 @@ def new_generate_content(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": model, contents, model_name = prepare_generate_content_args(args, kwargs) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"chat {model_name}", attributes={ @@ -290,6 +309,9 @@ async def new_async_generate_content( model, contents, model_name = prepare_generate_content_args(args, kwargs) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"chat {model_name}", attributes={ @@ -350,6 +372,9 @@ def new_embed_content(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": model_name, contents = prepare_embed_content_args(args, kwargs) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"embeddings {model_name}", attributes={ @@ -410,6 +435,9 @@ async def new_async_embed_content( model_name, contents = prepare_embed_content_args(args, kwargs) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"embeddings {model_name}", attributes={ diff --git a/sentry_sdk/integrations/google_genai/utils.py b/sentry_sdk/integrations/google_genai/utils.py index 464a812680..10699af42a 100644 --- a/sentry_sdk/integrations/google_genai/utils.py +++ b/sentry_sdk/integrations/google_genai/utils.py @@ -36,6 +36,7 @@ from sentry_sdk.utils import ( capture_internal_exceptions, event_from_exception, + nullcontext, safe_serialize, ) @@ -671,10 +672,12 @@ def _capture_tool_input( def _create_tool_span( tool_name: str, tool_doc: "Optional[str]" -) -> "Union[Span, StreamedSpan]": +) -> "Union[Span, StreamedSpan, nullcontext[None]]": """Create a span for tool execution.""" span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return nullcontext() span = sentry_sdk.traces.start_span( name=f"execute_tool {tool_name}", attributes={ @@ -712,22 +715,30 @@ def wrapped_tool(tool: "Tool | Callable[..., Any]") -> "Tool | Callable[..., Any @wraps(tool) async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any": with _create_tool_span(tool_name, tool_doc) as span: - set_on_span = ( - span.set_attribute - if isinstance(span, StreamedSpan) - else span.set_data - ) - # Capture tool input - tool_input = _capture_tool_input(args, kwargs, tool) - with capture_internal_exceptions(): - set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_input)) + if span is not None: + set_on_span = ( + span.set_attribute + if isinstance(span, StreamedSpan) + else span.set_data + ) + # Capture tool input + tool_input = _capture_tool_input(args, kwargs, tool) + with capture_internal_exceptions(): + set_on_span( + SPANDATA.GEN_AI_TOOL_INPUT, + safe_serialize(tool_input), + ) try: result = await tool(*args, **kwargs) # Capture tool output - with capture_internal_exceptions(): - set_on_span(SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result)) + if span is not None: + with capture_internal_exceptions(): + set_on_span( + SPANDATA.GEN_AI_TOOL_OUTPUT, + safe_serialize(result), + ) return result except Exception as exc: @@ -740,22 +751,30 @@ async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any": @wraps(tool) def sync_wrapped(*args: "Any", **kwargs: "Any") -> "Any": with _create_tool_span(tool_name, tool_doc) as span: - set_on_span = ( - span.set_attribute - if isinstance(span, StreamedSpan) - else span.set_data - ) - # Capture tool input - tool_input = _capture_tool_input(args, kwargs, tool) - with capture_internal_exceptions(): - set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_input)) + if span is not None: + set_on_span = ( + span.set_attribute + if isinstance(span, StreamedSpan) + else span.set_data + ) + # Capture tool input + tool_input = _capture_tool_input(args, kwargs, tool) + with capture_internal_exceptions(): + set_on_span( + SPANDATA.GEN_AI_TOOL_INPUT, + safe_serialize(tool_input), + ) try: result = tool(*args, **kwargs) # Capture tool output - with capture_internal_exceptions(): - set_on_span(SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result)) + if span is not None: + with capture_internal_exceptions(): + set_on_span( + SPANDATA.GEN_AI_TOOL_OUTPUT, + safe_serialize(result), + ) return result except Exception as exc: diff --git a/sentry_sdk/integrations/graphene.py b/sentry_sdk/integrations/graphene.py index 4938a5c0f3..ce2b545241 100644 --- a/sentry_sdk/integrations/graphene.py +++ b/sentry_sdk/integrations/graphene.py @@ -149,6 +149,10 @@ def graphql_span( ) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + yield + return + additional_attributes = {} if should_send_default_pii(): additional_attributes["graphql.document"] = source diff --git a/sentry_sdk/integrations/grpc/aio/client.py b/sentry_sdk/integrations/grpc/aio/client.py index d07b7f19be..97e9d828c0 100644 --- a/sentry_sdk/integrations/grpc/aio/client.py +++ b/sentry_sdk/integrations/grpc/aio/client.py @@ -52,6 +52,13 @@ async def intercept_unary_unary( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) + ) + return await continuation(client_call_details, request) with sentry_sdk.traces.start_span( name="unary unary call to %s" % method.decode(), attributes={ @@ -107,6 +114,13 @@ async def intercept_unary_stream( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) + ) + return await continuation(client_call_details, request) with sentry_sdk.traces.start_span( name="unary stream call to %s" % method.decode(), attributes={ diff --git a/sentry_sdk/integrations/grpc/client.py b/sentry_sdk/integrations/grpc/client.py index 5384a0a78f..fe9954445e 100644 --- a/sentry_sdk/integrations/grpc/client.py +++ b/sentry_sdk/integrations/grpc/client.py @@ -33,6 +33,13 @@ def intercept_unary_unary( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) + ) + return continuation(client_call_details, request) with sentry_sdk.traces.start_span( name="unary unary call to %s" % method, attributes={ @@ -84,6 +91,13 @@ def intercept_unary_stream( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) response: "UnaryStreamCall" if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) + ) + return continuation(client_call_details, request) with sentry_sdk.traces.start_span( name="unary stream call to %s" % method, attributes={ diff --git a/sentry_sdk/integrations/httpx.py b/sentry_sdk/integrations/httpx.py index a68f20b299..3322de564e 100644 --- a/sentry_sdk/integrations/httpx.py +++ b/sentry_sdk/integrations/httpx.py @@ -60,6 +60,9 @@ 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: + return real_send(self, request, **kwargs) + with sentry_sdk.traces.start_span( name="%s %s" % ( @@ -170,6 +173,9 @@ 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: + return await real_send(self, request, **kwargs) + with sentry_sdk.traces.start_span( name="%s %s" % ( diff --git a/sentry_sdk/integrations/httpx2.py b/sentry_sdk/integrations/httpx2.py index 25062aaa11..d327e320f6 100644 --- a/sentry_sdk/integrations/httpx2.py +++ b/sentry_sdk/integrations/httpx2.py @@ -60,6 +60,9 @@ 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: + return real_send(self, request, **kwargs) + with sentry_sdk.traces.start_span( name="%s %s" % ( @@ -170,6 +173,9 @@ 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: + return await real_send(self, request, **kwargs) + with sentry_sdk.traces.start_span( name="%s %s" % ( diff --git a/sentry_sdk/integrations/huey.py b/sentry_sdk/integrations/huey.py index a1c9d6a357..70814d4b7f 100644 --- a/sentry_sdk/integrations/huey.py +++ b/sentry_sdk/integrations/huey.py @@ -79,7 +79,18 @@ def _sentry_enqueue( sentry_sdk.get_client().options ) - span_ctx = None + no_headers_types = (PeriodicTask,) + tuple( + t for t in [HueyGroup, HueyChord] if t is not None + ) + + if is_span_streaming_enabled and sentry_sdk.traces.get_current_span() is None: + if not isinstance(item, no_headers_types): + item.kwargs["sentry_headers"] = { + BAGGAGE_HEADER_NAME: get_baggage(), + SENTRY_TRACE_HEADER_NAME: get_traceparent(), + } + return old_enqueue(self, item) + if is_span_streaming_enabled: span_ctx = sentry_sdk.traces.start_span( name=span_name, @@ -97,9 +108,6 @@ def _sentry_enqueue( ) span_ctx.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, self.name) - no_headers_types = (PeriodicTask,) + tuple( - t for t in [HueyGroup, HueyChord] if t is not None - ) with span_ctx: if not isinstance(item, no_headers_types): # Attach trace propagation data to task kwargs. We do diff --git a/sentry_sdk/integrations/huggingface_hub.py b/sentry_sdk/integrations/huggingface_hub.py index 835acc7279..f1afdfae6c 100644 --- a/sentry_sdk/integrations/huggingface_hub.py +++ b/sentry_sdk/integrations/huggingface_hub.py @@ -93,6 +93,8 @@ def new_huggingface_task(*args: "Any", **kwargs: "Any") -> "Any": span: "Union[Span, StreamedSpan]" if has_span_streaming_enabled(sentry_sdk.get_client().options): + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) span = sentry_sdk.traces.start_span( name=f"{operation_name} {model}", attributes={ diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 9dcbb189ce..9188f1a43e 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -296,7 +296,7 @@ def _create_span( op: str, name: str, origin: str, - ) -> "Union[sentry_sdk.tracing.Span, StreamedSpan]": + ) -> "Optional[Union[sentry_sdk.tracing.Span, StreamedSpan]]": span = None if parent_id: parent_span: "Optional[Union[sentry_sdk.tracing.Span, StreamedSpan]]" = ( @@ -318,17 +318,20 @@ def _create_span( if span is None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) - span = ( - sentry_sdk.traces.start_span( - name=name, - attributes={ - "sentry.op": op, - "sentry.origin": origin, - }, - ) - if span_streaming - else sentry_sdk.start_span(op=op, name=name, origin=origin) - ) + if span_streaming: + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=name, + attributes={ + "sentry.op": op, + "sentry.origin": origin, + }, + ) + else: + span = sentry_sdk.start_span(op=op, name=name, origin=origin) + + if span is None: + return None span.__enter__() self.span_map[run_id] = span @@ -375,6 +378,8 @@ def on_llm_start( name=f"text_completion {model}".strip(), origin=LangchainIntegration.origin, ) + if span is None: + return set_on_span = ( span.set_attribute if isinstance(span, StreamedSpan) else span.set_data @@ -455,6 +460,8 @@ def on_chat_model_start( name=f"chat {model}".strip(), origin=LangchainIntegration.origin, ) + if span is None: + return set_on_span = ( span.set_attribute if isinstance(span, StreamedSpan) else span.set_data @@ -672,6 +679,8 @@ def on_tool_start( name=f"execute_tool {tool_name}".strip(), origin=LangchainIntegration.origin, ) + if span is None: + return set_on_span = ( span.set_attribute if isinstance(span, StreamedSpan) else span.set_data @@ -1020,6 +1029,9 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": run_name, tools = _get_request_data(self, args, kwargs) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"invoke_agent {run_name}" if run_name else "invoke_agent", attributes={ @@ -1135,17 +1147,19 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": run_name, tools = _get_request_data(self, args, kwargs) if has_span_streaming_enabled(client.options): - span = sentry_sdk.traces.start_span( - name=f"invoke_agent {run_name}" if run_name else "invoke_agent", - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": LangchainIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - SPANDATA.GEN_AI_RESPONSE_STREAMING: True, - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"invoke_agent {run_name}" if run_name else "invoke_agent", + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": LangchainIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + SPANDATA.GEN_AI_RESPONSE_STREAMING: True, + }, + ) - if run_name: + if span is not None and run_name: span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name) else: start_span_function = get_start_span_function() @@ -1163,34 +1177,38 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if run_name: span.set_data(SPANDATA.GEN_AI_FUNCTION_ID, run_name) - _set_tools_on_span(span, tools) + if span is not None: + _set_tools_on_span(span, tools) - input = args[0].get("input") if len(args) >= 1 else None - if ( - input is not None - and should_send_default_pii() - and integration.include_prompts - ): - normalized_messages = normalize_message_roles([input]) - - client = sentry_sdk.get_client() - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if should_truncate_gen_ai_input(client.options) - else normalized_messages - ) - if messages_data is not None: - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, - unpack=False, + input = args[0].get("input") if len(args) >= 1 else None + if ( + input is not None + and should_send_default_pii() + and integration.include_prompts + ): + normalized_messages = normalize_message_roles([input]) + + client = sentry_sdk.get_client() + scope = sentry_sdk.get_current_scope() + messages_data = ( + truncate_and_annotate_messages(normalized_messages, span, scope) + if should_truncate_gen_ai_input(client.options) + else normalized_messages ) + if messages_data is not None: + set_data_normalized( + span, + SPANDATA.GEN_AI_REQUEST_MESSAGES, + messages_data, + unpack=False, + ) # Run the agent result = f(self, *args, **kwargs) + if span is None: + return result + old_iterator = result def new_iterator() -> "Iterator[Any]": @@ -1287,6 +1305,9 @@ def new_embedding_method(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": model_name = getattr(self, "model", None) or getattr(self, "model_name", None) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"embeddings {model_name}" if model_name else "embeddings", attributes={ @@ -1308,7 +1329,10 @@ def new_embedding_method(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": # Normalize to list format texts = input_data if isinstance(input_data, list) else [input_data] set_data_normalized( - span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, texts, unpack=False + span, + SPANDATA.GEN_AI_EMBEDDINGS_INPUT, + texts, + unpack=False, ) result = f(self, *args, **kwargs) @@ -1357,6 +1381,9 @@ async def new_async_embedding_method( model_name = getattr(self, "model", None) or getattr(self, "model_name", None) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"embeddings {model_name}" if model_name else "embeddings", attributes={ @@ -1378,7 +1405,10 @@ async def new_async_embedding_method( # Normalize to list format texts = input_data if isinstance(input_data, list) else [input_data] set_data_normalized( - span, SPANDATA.GEN_AI_EMBEDDINGS_INPUT, texts, unpack=False + span, + SPANDATA.GEN_AI_EMBEDDINGS_INPUT, + texts, + unpack=False, ) result = await f(self, *args, **kwargs) diff --git a/sentry_sdk/integrations/langgraph.py b/sentry_sdk/integrations/langgraph.py index 3d3856a913..2fc6444bde 100644 --- a/sentry_sdk/integrations/langgraph.py +++ b/sentry_sdk/integrations/langgraph.py @@ -174,6 +174,9 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=span_name, attributes={ @@ -286,6 +289,9 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(self, *args, **kwargs) + with sentry_sdk.traces.start_span( name=span_name, attributes={ diff --git a/sentry_sdk/integrations/litellm.py b/sentry_sdk/integrations/litellm.py index 49ead6b068..4819b82f28 100644 --- a/sentry_sdk/integrations/litellm.py +++ b/sentry_sdk/integrations/litellm.py @@ -99,6 +99,8 @@ def _input_callback(kwargs: "Dict[str, Any]") -> None: # Start a new span/transaction if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return span = sentry_sdk.traces.start_span( name=f"{operation} {model}", attributes={ diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index f0c90a7921..cb7aa87ae9 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -164,6 +164,8 @@ async def _create_span_call( middleware_name = self.__class__.__name__ if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await old_call(self, scope, receive, send) with sentry_sdk.traces.start_span( name=middleware_name, attributes={ @@ -179,6 +181,8 @@ async def _sentry_receive( ) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]": if client.get_integration(LitestarIntegration) is None: return await receive(*args, **kwargs) + if sentry_sdk.traces.get_current_span() is None: + return await receive(*args, **kwargs) with sentry_sdk.traces.start_span( name=getattr(receive, "__qualname__", str(receive)), attributes={ @@ -197,6 +201,8 @@ async def _sentry_receive( async def _sentry_send(message: "Message") -> None: if client.get_integration(LitestarIntegration) is None: return await send(message) + if sentry_sdk.traces.get_current_span() is None: + return await send(message) with sentry_sdk.traces.start_span( name=getattr(send, "__qualname__", str(send)), attributes={ diff --git a/sentry_sdk/integrations/mcp.py b/sentry_sdk/integrations/mcp.py index dbfaa912c3..9a4049bf10 100644 --- a/sentry_sdk/integrations/mcp.py +++ b/sentry_sdk/integrations/mcp.py @@ -567,15 +567,18 @@ async def _handler_wrapper( # Start span and execute with isolation_scope_context: with current_scope_context: - span_mgr: "Union[Span, StreamedSpan]" + span_mgr: "Union[Span, StreamedSpan, ContextManager[None]]" if span_streaming: - span_mgr = sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.MCP_SERVER, - "sentry.origin": MCPIntegration.origin, - }, - ) + if sentry_sdk.traces.get_current_span() is not None: + span_mgr = sentry_sdk.traces.start_span( + name=span_name, + attributes={ + "sentry.op": OP.MCP_SERVER, + "sentry.origin": MCPIntegration.origin, + }, + ) + else: + span_mgr = nullcontext() else: span_mgr = get_start_span_function()( op=OP.MCP_SERVER, @@ -584,40 +587,41 @@ async def _handler_wrapper( ) with span_mgr as span: - # Set input span data - _set_span_input_data( - span, - handler_name, - span_data_key, - mcp_method_name, - arguments, - request_id, - session_id, - mcp_transport, - ) + if span is not None: + # Set input span data + _set_span_input_data( + span, + handler_name, + span_data_key, + mcp_method_name, + arguments, + request_id, + session_id, + mcp_transport, + ) - # For resources, extract and set protocol - if handler_type == "resource": - uri = None - if params is not None: - uri = getattr(params, "uri", None) - - # v1 scenario - if ServerRequestContext is None: - if original_args: - uri = original_args[0] - else: - uri = original_kwargs.get("uri") - - protocol = None - if uri is not None and hasattr(uri, "scheme"): - protocol = uri.scheme - elif handler_name and "://" in handler_name: - protocol = handler_name.split("://")[0] - if protocol: - _set_span_data_attribute( - span, SPANDATA.MCP_RESOURCE_PROTOCOL, protocol - ) + # For resources, extract and set protocol + if handler_type == "resource": + uri = None + if params is not None: + uri = getattr(params, "uri", None) + + # v1 scenario + if ServerRequestContext is None: + if original_args: + uri = original_args[0] + else: + uri = original_kwargs.get("uri") + + protocol = None + if uri is not None and hasattr(uri, "scheme"): + protocol = uri.scheme + elif handler_name and "://" in handler_name: + protocol = handler_name.split("://")[0] + if protocol: + _set_span_data_attribute( + span, SPANDATA.MCP_RESOURCE_PROTOCOL, protocol + ) try: # Execute the async handler @@ -630,14 +634,15 @@ async def _handler_wrapper( except Exception as e: # Set error flag for tools - if handler_type == "tool": + if span is not None and handler_type == "tool": _set_span_data_attribute( span, SPANDATA.MCP_TOOL_RESULT_IS_ERROR, True ) sentry_sdk.capture_exception(e) raise - _set_span_output_data(span, result, result_data_key, handler_type) + if span is not None: + _set_span_output_data(span, result, result_data_key, handler_type) return result diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 186c665ed1..7004f213e9 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -729,6 +729,9 @@ def _new_sync_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": is_streaming_response = kwargs.get("stream", False) or False if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) + span = sentry_sdk.traces.start_span( name=f"chat {model}", attributes={ @@ -810,6 +813,9 @@ async def _new_async_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> is_streaming_response = kwargs.get("stream", False) or False if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(*args, **kwargs) + span = sentry_sdk.traces.start_span( name=f"chat {model}", attributes={ @@ -1227,6 +1233,9 @@ def _new_sync_embeddings_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any model = kwargs.get("model") if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) + with sentry_sdk.traces.start_span( name=f"embeddings {model}", attributes={ @@ -1285,6 +1294,9 @@ async def _new_async_embeddings_create( model = kwargs.get("model") if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(*args, **kwargs) + with sentry_sdk.traces.start_span( name=f"embeddings {model}", attributes={ @@ -1368,6 +1380,9 @@ def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any" is_streaming_response = kwargs.get("stream", False) or False if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return f(*args, **kwargs) + span = sentry_sdk.traces.start_span( name=f"responses {model}", attributes={ @@ -1438,6 +1453,9 @@ async def _new_async_responses_create(f: "Any", *args: "Any", **kwargs: "Any") - is_streaming_response = kwargs.get("stream", False) or False if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return await f(*args, **kwargs) + span = sentry_sdk.traces.start_span( name=f"responses {model}", attributes={ diff --git a/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py b/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py index 758f06db8d..5355722fe2 100644 --- a/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +++ b/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py @@ -18,9 +18,12 @@ def agent_workflow_span( # Create a transaction or a span if an transaction is already active span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"{agent.name} workflow", attributes={"sentry.origin": SPAN_ORIGIN} - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"{agent.name} workflow", + attributes={"sentry.origin": SPAN_ORIGIN}, + ) return span diff --git a/sentry_sdk/integrations/openai_agents/spans/ai_client.py b/sentry_sdk/integrations/openai_agents/spans/ai_client.py index f4f02cb674..0de8384aff 100644 --- a/sentry_sdk/integrations/openai_agents/spans/ai_client.py +++ b/sentry_sdk/integrations/openai_agents/spans/ai_client.py @@ -32,14 +32,16 @@ def ai_client_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"chat {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_CHAT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "chat", - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"chat {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_CHAT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "chat", + }, + ) else: span = sentry_sdk.start_span( op=OP.GEN_AI_CHAT, @@ -49,8 +51,9 @@ def ai_client_span( # TODO-anton: remove hardcoded stuff and replace something that also works for embedding and so on span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "chat") - _set_agent_data(span, agent) - _set_input_data(span, get_response_kwargs) + if span is not None: + _set_agent_data(span, agent) + _set_input_data(span, get_response_kwargs) return span diff --git a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py index fd3a430951..93f5ae71a5 100644 --- a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +++ b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py @@ -20,18 +20,20 @@ def execute_tool_span( ) -> "Union[sentry_sdk.tracing.Span, StreamedSpan]": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"execute_tool {tool.name}", - attributes={ - "sentry.op": OP.GEN_AI_EXECUTE_TOOL, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "execute_tool", - SPANDATA.GEN_AI_TOOL_NAME: tool.name, - SPANDATA.GEN_AI_TOOL_DESCRIPTION: tool.description, - }, - ) - - set_on_span = span.set_attribute + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"execute_tool {tool.name}", + attributes={ + "sentry.op": OP.GEN_AI_EXECUTE_TOOL, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "execute_tool", + SPANDATA.GEN_AI_TOOL_NAME: tool.name, + SPANDATA.GEN_AI_TOOL_DESCRIPTION: tool.description, + }, + ) + + set_on_span = span.set_attribute if span is not None else None else: span = sentry_sdk.start_span( op=OP.GEN_AI_EXECUTE_TOOL, @@ -46,7 +48,7 @@ def execute_tool_span( set_on_span = span.set_data - if should_send_default_pii(): + if span is not None and should_send_default_pii(): input = args[1] set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, input) diff --git a/sentry_sdk/integrations/openai_agents/spans/handoff.py b/sentry_sdk/integrations/openai_agents/spans/handoff.py index ea91464afb..4d1c987de5 100644 --- a/sentry_sdk/integrations/openai_agents/spans/handoff.py +++ b/sentry_sdk/integrations/openai_agents/spans/handoff.py @@ -15,6 +15,9 @@ def handoff_span( ) -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return + with sentry_sdk.traces.start_span( name=f"handoff from {from_agent.name} to {to_agent_name}", attributes={ diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index c21145ac4a..2d1c65d38b 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -30,14 +30,16 @@ def invoke_agent_span( ) -> "Union[sentry_sdk.tracing.Span, StreamedSpan]": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"invoke_agent {agent.name}", - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"invoke_agent {agent.name}", + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) else: start_span_function = get_start_span_function() span = start_span_function( @@ -49,53 +51,54 @@ def invoke_agent_span( span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") - if should_send_default_pii(): - messages = [] - if agent.instructions: - message = ( - agent.instructions - if isinstance(agent.instructions, str) - else safe_serialize(agent.instructions) - ) - messages.append( - { - "content": [{"text": message, "type": "text"}], - "role": "system", - } - ) - - original_input = kwargs.get("original_input") - if original_input is not None: - message = ( - original_input - if isinstance(original_input, str) - else safe_serialize(original_input) - ) - messages.append( - { - "content": [{"text": message, "type": "text"}], - "role": "user", - } - ) + if span is not None: + if should_send_default_pii(): + messages = [] + if agent.instructions: + message = ( + agent.instructions + if isinstance(agent.instructions, str) + else safe_serialize(agent.instructions) + ) + messages.append( + { + "content": [{"text": message, "type": "text"}], + "role": "system", + } + ) - if len(messages) > 0: - normalized_messages = normalize_message_roles(messages) - client = sentry_sdk.get_client() - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if should_truncate_gen_ai_input(client.options) - else normalized_messages - ) - if messages_data is not None: - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, - unpack=False, + original_input = kwargs.get("original_input") + if original_input is not None: + message = ( + original_input + if isinstance(original_input, str) + else safe_serialize(original_input) + ) + messages.append( + { + "content": [{"text": message, "type": "text"}], + "role": "user", + } ) - _set_agent_data(span, agent) + if len(messages) > 0: + normalized_messages = normalize_message_roles(messages) + client = sentry_sdk.get_client() + scope = sentry_sdk.get_current_scope() + messages_data = ( + truncate_and_annotate_messages(normalized_messages, span, scope) + if should_truncate_gen_ai_input(client.options) + else normalized_messages + ) + if messages_data is not None: + set_data_normalized( + span, + SPANDATA.GEN_AI_REQUEST_MESSAGES, + messages_data, + unpack=False, + ) + + _set_agent_data(span, agent) return span diff --git a/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py b/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py index 27deb0c55c..21aff52575 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py @@ -282,15 +282,17 @@ def ai_client_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"chat {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_CHAT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "chat", - SPANDATA.GEN_AI_RESPONSE_STREAMING: get_is_streaming(), - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"chat {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_CHAT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "chat", + SPANDATA.GEN_AI_RESPONSE_STREAMING: get_is_streaming(), + }, + ) else: span = sentry_sdk.start_span( op=OP.GEN_AI_CHAT, @@ -302,16 +304,17 @@ def ai_client_span( # Set streaming flag from contextvar span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, get_is_streaming()) - _set_agent_data(span, agent) - _set_model_data(span, model, model_settings) + if span is not None: + _set_agent_data(span, agent) + _set_model_data(span, model, model_settings) - # Add available tools if agent is available - agent_obj = agent or get_current_agent() - _set_available_tools(span, agent_obj) + # Add available tools if agent is available + agent_obj = agent or get_current_agent() + _set_available_tools(span, agent_obj) - # Set input messages (full conversation history) - if messages: - _set_input_messages(span, messages) + # Set input messages (full conversation history) + if messages: + _set_input_messages(span, messages) return span diff --git a/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py b/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py index 7648c1418a..edc00906f2 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py @@ -31,17 +31,19 @@ def execute_tool_span( """ span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"execute_tool {tool_name}", - attributes={ - "sentry.op": OP.GEN_AI_EXECUTE_TOOL, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "execute_tool", - SPANDATA.GEN_AI_TOOL_NAME: tool_name, - }, - ) - - set_on_span = span.set_attribute + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"execute_tool {tool_name}", + attributes={ + "sentry.op": OP.GEN_AI_EXECUTE_TOOL, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "execute_tool", + SPANDATA.GEN_AI_TOOL_NAME: tool_name, + }, + ) + + set_on_span = span.set_attribute if span is not None else None else: span = sentry_sdk.start_span( op=OP.GEN_AI_EXECUTE_TOOL, @@ -54,16 +56,17 @@ def execute_tool_span( set_on_span = span.set_data - if tool_definition is not None and hasattr(tool_definition, "description"): - set_on_span( - SPANDATA.GEN_AI_TOOL_DESCRIPTION, - tool_definition.description, - ) + if span is not None: + if tool_definition is not None and hasattr(tool_definition, "description"): + set_on_span( + SPANDATA.GEN_AI_TOOL_DESCRIPTION, + tool_definition.description, + ) - _set_agent_data(span, agent) + _set_agent_data(span, agent) - if _should_send_prompts() and tool_args is not None: - set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_args)) + if _should_send_prompts() and tool_args is not None: + set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_args)) return span diff --git a/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py b/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py index f0c68e85ba..73bba0c86a 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py @@ -51,14 +51,16 @@ def invoke_agent_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"invoke_agent {name}", - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"invoke_agent {name}", + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) else: span = get_start_span_function()( op=OP.GEN_AI_INVOKE_AGENT, @@ -68,85 +70,86 @@ def invoke_agent_span( span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") - _set_agent_data(span, agent) - _set_model_data(span, model, model_settings) - _set_available_tools(span, agent) - - # Add user prompt and system prompts if available and prompts are enabled - if _should_send_prompts(): - messages = [] - - # Add system prompts (both instructions and system_prompt) - system_texts = [] - - if agent: - # Check for system_prompt - system_prompts = getattr(agent, "_system_prompts", None) or [] - for prompt in system_prompts: - if isinstance(prompt, str): - system_texts.append(prompt) - - # Check for instructions (stored in _instructions) - instructions = getattr(agent, "_instructions", None) - if instructions: - if isinstance(instructions, str): - system_texts.append(instructions) - elif isinstance(instructions, (list, tuple)): - for instr in instructions: - if isinstance(instr, str): - system_texts.append(instr) - elif callable(instr): - # Skip dynamic/callable instructions - pass - - # Add all system texts as system messages - for system_text in system_texts: - messages.append( - { - "content": [{"text": system_text, "type": "text"}], - "role": "system", - } - ) - - # Add user prompt - if user_prompt: - if isinstance(user_prompt, str): + if span is not None: + _set_agent_data(span, agent) + _set_model_data(span, model, model_settings) + _set_available_tools(span, agent) + + # Add user prompt and system prompts if available and prompts are enabled + if _should_send_prompts(): + messages = [] + + # Add system prompts (both instructions and system_prompt) + system_texts = [] + + if agent: + # Check for system_prompt + system_prompts = getattr(agent, "_system_prompts", None) or [] + for prompt in system_prompts: + if isinstance(prompt, str): + system_texts.append(prompt) + + # Check for instructions (stored in _instructions) + instructions = getattr(agent, "_instructions", None) + if instructions: + if isinstance(instructions, str): + system_texts.append(instructions) + elif isinstance(instructions, (list, tuple)): + for instr in instructions: + if isinstance(instr, str): + system_texts.append(instr) + elif callable(instr): + # Skip dynamic/callable instructions + pass + + # Add all system texts as system messages + for system_text in system_texts: messages.append( { - "content": [{"text": user_prompt, "type": "text"}], - "role": "user", + "content": [{"text": system_text, "type": "text"}], + "role": "system", } ) - elif isinstance(user_prompt, list): - # Handle list of user content - content = [] - for item in user_prompt: - if isinstance(item, str): - content.append({"text": item, "type": "text"}) - elif ImageUrl and isinstance(item, ImageUrl): - content.append(_serialize_image_url_item(item)) - elif BinaryContent and isinstance(item, BinaryContent): - content.append(_serialize_binary_content_item(item)) - if content: + + # Add user prompt + if user_prompt: + if isinstance(user_prompt, str): messages.append( { - "content": content, + "content": [{"text": user_prompt, "type": "text"}], "role": "user", } ) - - if messages: - normalized_messages = normalize_message_roles(messages) - client = sentry_sdk.get_client() - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if should_truncate_gen_ai_input(client.options) - else normalized_messages - ) - set_data_normalized( - span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False - ) + elif isinstance(user_prompt, list): + # Handle list of user content + content = [] + for item in user_prompt: + if isinstance(item, str): + content.append({"text": item, "type": "text"}) + elif ImageUrl and isinstance(item, ImageUrl): + content.append(_serialize_image_url_item(item)) + elif BinaryContent and isinstance(item, BinaryContent): + content.append(_serialize_binary_content_item(item)) + if content: + messages.append( + { + "content": content, + "role": "user", + } + ) + + if messages: + normalized_messages = normalize_message_roles(messages) + client = sentry_sdk.get_client() + scope = sentry_sdk.get_current_scope() + messages_data = ( + truncate_and_annotate_messages(normalized_messages, span, scope) + if should_truncate_gen_ai_input(client.options) + else normalized_messages + ) + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False + ) return span diff --git a/sentry_sdk/integrations/pymongo.py b/sentry_sdk/integrations/pymongo.py index 2616f4d5a3..8ce8281ab4 100644 --- a/sentry_sdk/integrations/pymongo.py +++ b/sentry_sdk/integrations/pymongo.py @@ -152,6 +152,9 @@ def started(self, event: "CommandStartedEvent") -> None: query = json.dumps(command, default=str) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return + span_first_data = { "db.operation.name": operation_name, "db.collection.name": collection_name, diff --git a/sentry_sdk/integrations/pyreqwest.py b/sentry_sdk/integrations/pyreqwest.py index aae68c4c10..7ca73b370a 100644 --- a/sentry_sdk/integrations/pyreqwest.py +++ b/sentry_sdk/integrations/pyreqwest.py @@ -18,6 +18,7 @@ SENSITIVE_DATA_SUBSTITUTE, capture_internal_exceptions, logger, + nullcontext, parse_url, ) @@ -88,18 +89,22 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - 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) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = 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, + }, + ) + with span_ctx as span: + if span is not None: + 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) if should_propagate_trace(sentry_sdk.get_client(), str(request.url)): for ( @@ -119,8 +124,9 @@ def _sentry_pyreqwest_span(request: "Request") -> "Generator[Any, None, None]": 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 @@ -171,7 +177,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 @@ -191,7 +197,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 diff --git a/sentry_sdk/integrations/ray.py b/sentry_sdk/integrations/ray.py index f723a96f3c..f48bd59791 100644 --- a/sentry_sdk/integrations/ray.py +++ b/sentry_sdk/integrations/ray.py @@ -156,6 +156,23 @@ def _remote_method_with_header_propagation( ) if span_streaming: function_name = qualname_from_function(user_f) + + if sentry_sdk.traces.get_current_span() is None: + tracing = { + k: v + for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers() + } + try: + result = old_remote_method( + *args, **kwargs, _sentry_tracing=tracing + ) + except Exception: + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + with sentry_sdk.traces.start_span( name="unknown Ray task" if function_name is None diff --git a/sentry_sdk/integrations/redis/_async_common.py b/sentry_sdk/integrations/redis/_async_common.py index 8fc3d0c3a9..bd83d22191 100644 --- a/sentry_sdk/integrations/redis/_async_common.py +++ b/sentry_sdk/integrations/redis/_async_common.py @@ -46,6 +46,8 @@ async def _sentry_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span: "Union[Span, StreamedSpan]" if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return await old_execute(self, *args, **kwargs) span = sentry_sdk.traces.start_span( name="redis.pipeline.execute", attributes={ @@ -103,6 +105,9 @@ async def _sentry_execute_command( span_streaming = has_span_streaming_enabled(client.options) + if span_streaming and sentry_sdk.traces.get_current_span() is None: + return await old_execute_command(self, name, *args, **kwargs) + cache_properties = _compile_cache_span_properties( name, args, diff --git a/sentry_sdk/integrations/redis/_sync_common.py b/sentry_sdk/integrations/redis/_sync_common.py index 58d686b099..3afa7f282c 100644 --- a/sentry_sdk/integrations/redis/_sync_common.py +++ b/sentry_sdk/integrations/redis/_sync_common.py @@ -43,6 +43,8 @@ def sentry_patched_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span: "Union[Span, StreamedSpan]" if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return old_execute(self, *args, **kwargs) span = sentry_sdk.traces.start_span( name="redis.pipeline.execute", attributes={ @@ -102,6 +104,9 @@ def sentry_patched_execute_command( span_streaming = has_span_streaming_enabled(client.options) + if span_streaming and sentry_sdk.traces.get_current_span() is None: + return old_execute_command(self, name, *args, **kwargs) + cache_properties = _compile_cache_span_properties( name, args, diff --git a/sentry_sdk/integrations/rust_tracing.py b/sentry_sdk/integrations/rust_tracing.py index 622e3c17af..d02b7fb5ab 100644 --- a/sentry_sdk/integrations/rust_tracing.py +++ b/sentry_sdk/integrations/rust_tracing.py @@ -208,6 +208,9 @@ def on_new_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return None + sentry_span = sentry_sdk.traces.start_span( name=sentry_span_name, attributes={ diff --git a/sentry_sdk/integrations/socket.py b/sentry_sdk/integrations/socket.py index 775170fb9f..c719f14227 100644 --- a/sentry_sdk/integrations/socket.py +++ b/sentry_sdk/integrations/socket.py @@ -57,6 +57,9 @@ def create_connection( return real_create_connection(address, timeout, source_address) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return real_create_connection(address, timeout, source_address) + with sentry_sdk.traces.start_span( name=_get_span_description(address[0], address[1]), attributes={ @@ -105,6 +108,9 @@ def getaddrinfo( return real_getaddrinfo(host, port, family, type, proto, flags) if has_span_streaming_enabled(client.options): + if sentry_sdk.traces.get_current_span() is None: + return real_getaddrinfo(host, port, family, type, proto, flags) + with sentry_sdk.traces.start_span( name=_get_span_description(host, port), attributes={ diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 3f9fbf4d8e..74effb1516 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -35,6 +35,7 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, + nullcontext, parse_version, transaction_from_function, ) @@ -198,6 +199,8 @@ async def _create_span_call( def _start_middleware_span(op: str, name: str) -> "Any": if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + return nullcontext() return sentry_sdk.traces.start_span( name=name, attributes={ diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index 1eebd37e84..3b759a62b7 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -10,6 +10,7 @@ from sentry_sdk.utils import ( ensure_integration_enabled, event_from_exception, + nullcontext, transaction_from_function, ) @@ -151,6 +152,8 @@ async def _create_span_call( def _start_middleware_span(op: str, name: str) -> "Any": if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + return nullcontext() return sentry_sdk.traces.start_span( name=name, attributes={ diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index 82f30f2dda..1cb1b62a5f 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -114,6 +114,9 @@ def putrequest( span_streaming = has_span_streaming_enabled(client.options) span: "Union[Span, StreamedSpan]" if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return real_putrequest(self, method, url, *args, **kwargs) + span = sentry_sdk.traces.start_span( name="%s %s" % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), @@ -303,6 +306,9 @@ def sentry_patched_popen_init( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) span: "Union[Span, StreamedSpan]" if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return old_popen_init(self, *a, **kw) + span = sentry_sdk.traces.start_span( name=description, attributes={ @@ -353,6 +359,8 @@ def sentry_patched_popen_wait( ) -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return old_popen_wait(self, *a, **kw) with sentry_sdk.traces.start_span( name=OP.SUBPROCESS_WAIT, attributes={ @@ -380,6 +388,8 @@ def sentry_patched_popen_communicate( ) -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return old_popen_communicate(self, *a, **kw) with sentry_sdk.traces.start_span( name=OP.SUBPROCESS_COMMUNICATE, attributes={ diff --git a/sentry_sdk/integrations/strawberry.py b/sentry_sdk/integrations/strawberry.py index 5f00e8bf6d..4e41133f3a 100644 --- a/sentry_sdk/integrations/strawberry.py +++ b/sentry_sdk/integrations/strawberry.py @@ -188,6 +188,10 @@ def on_operation(self) -> "Generator[None, None, None]": client = sentry_sdk.get_client() is_span_streaming_enabled = has_span_streaming_enabled(client.options) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + yield + return + additional_attributes: "dict[str, Any]" = {} if should_send_default_pii(): @@ -244,6 +248,10 @@ def on_validate(self) -> "Generator[None, None, None]": is_span_streaming_enabled = has_span_streaming_enabled(client.options) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + yield + return + validation_span = sentry_sdk.traces.start_span( name="validation", attributes={ @@ -272,6 +280,10 @@ def on_parse(self) -> "Generator[None, None, None]": is_span_streaming_enabled = has_span_streaming_enabled(client.options) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + yield + return + parsing_span = sentry_sdk.traces.start_span( name="parsing", attributes={ @@ -333,6 +345,9 @@ async def resolve( client = sentry_sdk.get_client() is_span_streaming_enabled = has_span_streaming_enabled(client.options) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + return await self._resolve(_next, root, info, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"resolving {field_path}", attributes={ @@ -372,6 +387,9 @@ def resolve( client = sentry_sdk.get_client() is_span_streaming_enabled = has_span_streaming_enabled(client.options) if is_span_streaming_enabled: + if sentry_sdk.traces.get_current_span() is None: + return _next(root, info, *args, **kwargs) + with sentry_sdk.traces.start_span( name=f"resolving {field_path}", attributes={