From d4cedbc5ccf383f1c7a719c3ad461628fb5fb21f Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 13 Jul 2026 14:47:07 +0200 Subject: [PATCH 1/4] ref: Move nullcontext to sentry_sdk.utils Move nullcontext from _wsgi_common to utils so it can be imported without pulling in WSGI-specific code. Update all consumers. Co-Authored-By: Claude Opus 4.6 --- sentry_sdk/integrations/_wsgi_common.py | 9 +-------- sentry_sdk/integrations/asgi.py | 2 +- sentry_sdk/integrations/asyncio.py | 2 +- sentry_sdk/integrations/mcp.py | 3 +-- sentry_sdk/integrations/wsgi.py | 2 +- sentry_sdk/utils.py | 6 ++++++ 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py index cf1a365209..16a7639189 100644 --- a/sentry_sdk/integrations/_wsgi_common.py +++ b/sentry_sdk/integrations/_wsgi_common.py @@ -1,5 +1,4 @@ import json -from contextlib import contextmanager from copy import deepcopy import sentry_sdk @@ -18,7 +17,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Dict, Iterator, Mapping, MutableMapping, Optional, Union + from typing import Any, Dict, Mapping, MutableMapping, Optional, Union from sentry_sdk._types import Event, HttpStatusCodeRange @@ -52,12 +51,6 @@ ) -# This noop context manager can be replaced with "from contextlib import nullcontext" when we drop Python 3.6 support -@contextmanager -def nullcontext() -> "Iterator[None]": - yield - - def request_body_within_bounds( client: "Optional[sentry_sdk.client.BaseClient]", content_length: int ) -> bool: diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 8b1ff5e2a3..a360d7af36 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -24,7 +24,6 @@ ) from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, - nullcontext, ) from sentry_sdk.scope import Scope, should_send_default_pii from sentry_sdk.sessions import track_session @@ -49,6 +48,7 @@ capture_internal_exceptions, event_from_exception, logger, + nullcontext, qualname_from_function, reraise, transaction_from_function, diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index 4b3f6d330e..bf30a69e67 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -4,7 +4,6 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.integrations import DidNotEnable, Integration -from sentry_sdk.integrations._wsgi_common import nullcontext from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing import Span from sentry_sdk.tracing_utils import has_span_streaming_enabled @@ -13,6 +12,7 @@ event_from_exception, is_internal_task, logger, + nullcontext, reraise, ) diff --git a/sentry_sdk/integrations/mcp.py b/sentry_sdk/integrations/mcp.py index 79381fe06e..dbfaa912c3 100644 --- a/sentry_sdk/integrations/mcp.py +++ b/sentry_sdk/integrations/mcp.py @@ -15,11 +15,10 @@ from sentry_sdk.ai.utils import _set_span_data_attribute, get_start_span_function from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import DidNotEnable, Integration -from sentry_sdk.integrations._wsgi_common import nullcontext from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import package_version, safe_serialize +from sentry_sdk.utils import nullcontext, package_version, safe_serialize MCP_PACKAGE_VERSION = package_version("mcp") diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index e776ed915a..168b889b60 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -9,7 +9,6 @@ from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, _filter_headers, - nullcontext, ) from sentry_sdk.scope import Scope, should_send_default_pii, use_isolation_scope from sentry_sdk.sessions import track_session @@ -20,6 +19,7 @@ ContextVar, capture_internal_exceptions, event_from_exception, + nullcontext, reraise, ) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 0963015351..5aa533dea0 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -2176,3 +2176,9 @@ def serialize_attribute(val: "AttributeValue") -> "SerializedAttributeValue": # Coerce to string if we don't know what to do with the value. This should # never happen as we pre-format early in format_attribute, but let's be safe. return {"value": safe_repr(val), "type": "string"} + + +# This noop context manager can be replaced with "from contextlib import nullcontext" when we drop Python 3.6 support +@contextmanager +def nullcontext() -> "Iterator[None]": + yield From 6272d368630b3ae17dc94be1820dc3bb35acc967 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 13 Jul 2026 15:17:11 +0200 Subject: [PATCH 2/4] fix(tracing): Skip child span creation in streaming path when no current span MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In span streaming mode, sentry_sdk.traces.start_span() without parent_span=None creates a new root segment when there's no current span. For integrations that create child spans (not root/transaction spans), this is wrong — they should do nothing instead. Add get_current_span() checks to all streaming-path child span creation across 52 integration files. Root span creators (those with parent_span=None in the non-streaming path) are left untouched. Co-Authored-By: Claude Opus 4.6 --- sentry_sdk/integrations/aiohttp.py | 6 +- sentry_sdk/integrations/aiomysql.py | 10 +- sentry_sdk/integrations/anthropic.py | 8 + sentry_sdk/integrations/arq.py | 18 +- sentry_sdk/integrations/asyncio.py | 15 +- sentry_sdk/integrations/asyncpg.py | 10 +- sentry_sdk/integrations/boto3.py | 2 + sentry_sdk/integrations/celery/__init__.py | 92 +++--- sentry_sdk/integrations/clickhouse_driver.py | 21 +- sentry_sdk/integrations/cohere.py | 57 ++-- sentry_sdk/integrations/django/__init__.py | 61 ++-- sentry_sdk/integrations/django/asgi.py | 18 +- sentry_sdk/integrations/django/caching.py | 101 +++--- sentry_sdk/integrations/django/middleware.py | 2 + .../integrations/django/signals_handlers.py | 20 +- sentry_sdk/integrations/django/tasks.py | 19 +- sentry_sdk/integrations/django/templates.py | 36 ++- sentry_sdk/integrations/django/views.py | 35 +- .../integrations/google_genai/__init__.py | 245 ++++++++------ sentry_sdk/integrations/google_genai/utils.py | 65 ++-- sentry_sdk/integrations/graphene.py | 4 + sentry_sdk/integrations/grpc/aio/client.py | 46 +-- sentry_sdk/integrations/grpc/client.py | 46 +-- sentry_sdk/integrations/httpx.py | 6 + sentry_sdk/integrations/httpx2.py | 6 + sentry_sdk/integrations/huey.py | 20 +- sentry_sdk/integrations/huggingface_hub.py | 2 + sentry_sdk/integrations/langchain.py | 302 ++++++++++-------- sentry_sdk/integrations/langgraph.py | 56 ++-- sentry_sdk/integrations/litellm.py | 2 + sentry_sdk/integrations/litestar.py | 67 ++-- sentry_sdk/integrations/mcp.py | 91 +++--- sentry_sdk/integrations/openai.py | 18 ++ .../openai_agents/spans/agent_workflow.py | 9 +- .../openai_agents/spans/ai_client.py | 23 +- .../openai_agents/spans/execute_tool.py | 28 +- .../openai_agents/spans/handoff.py | 29 +- .../openai_agents/spans/invoke_agent.py | 107 ++++--- .../pydantic_ai/spans/ai_client.py | 37 ++- .../pydantic_ai/spans/execute_tool.py | 41 +-- .../pydantic_ai/spans/invoke_agent.py | 159 ++++----- sentry_sdk/integrations/pymongo.py | 3 + sentry_sdk/integrations/pyreqwest.py | 38 ++- sentry_sdk/integrations/ray.py | 22 +- .../integrations/redis/_async_common.py | 5 + sentry_sdk/integrations/redis/_sync_common.py | 5 + sentry_sdk/integrations/rust_tracing.py | 3 + sentry_sdk/integrations/socket.py | 71 ++-- sentry_sdk/integrations/starlette.py | 3 + sentry_sdk/integrations/starlite.py | 3 + sentry_sdk/integrations/stdlib.py | 47 ++- sentry_sdk/integrations/strawberry.py | 18 ++ 52 files changed, 1271 insertions(+), 887 deletions(-) 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..3f1d6728c9 100644 --- a/sentry_sdk/integrations/aiomysql.py +++ b/sentry_sdk/integrations/aiomysql.py @@ -14,6 +14,7 @@ ) from sentry_sdk.utils import ( capture_internal_exceptions, + nullcontext, parse_version, ) @@ -179,9 +180,12 @@ async def _inner(self: "Connection") -> T: "sentry.origin": AioMySQLIntegration.origin, } | breadcrumb_data - with sentry_sdk.traces.start_span( - name="connect", attributes=span_attributes - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="connect", attributes=span_attributes + ) + with span_ctx as span: with capture_internal_exceptions(): sentry_sdk.add_breadcrumb( message="connect", category="query", data=breadcrumb_data 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..e5c3e69ba5 100644 --- a/sentry_sdk/integrations/arq.py +++ b/sentry_sdk/integrations/arq.py @@ -14,6 +14,7 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, + nullcontext, parse_version, reraise, ) @@ -79,13 +80,16 @@ async def _sentry_enqueue_job( return await old_enqueue_job(self, function, *args, **kwargs) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=function, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_ARQ, - "sentry.origin": ArqIntegration.origin, - }, - ): + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=function, + attributes={ + "sentry.op": OP.QUEUE_SUBMIT_ARQ, + "sentry.origin": ArqIntegration.origin, + }, + ) + with span_ctx: return await old_enqueue_job(self, function, *args, **kwargs) with sentry_sdk.start_span( 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..474c0d17ca 100644 --- a/sentry_sdk/integrations/asyncpg.py +++ b/sentry_sdk/integrations/asyncpg.py @@ -16,6 +16,7 @@ ) from sentry_sdk.utils import ( capture_internal_exceptions, + nullcontext, parse_version, ) @@ -235,9 +236,12 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T": except IndexError: pass - with sentry_sdk.traces.start_span( - name="connect", attributes=span_attributes - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="connect", attributes=span_attributes + ) + with span_ctx as span: with capture_internal_exceptions(): sentry_sdk.add_breadcrumb( message="connect", category="query", data=span_attributes 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..71e2aa9825 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -23,6 +23,7 @@ SENSITIVE_DATA_SUBSTITUTE, capture_internal_exceptions, event_from_exception, + nullcontext, reraise, ) @@ -419,59 +420,64 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": span_streaming = has_span_streaming_enabled(client.options) try: - span: "Union[Span, StreamedSpan]" + span_ctx: "Any" if span_streaming: - span = sentry_sdk.traces.start_span( - name=task.name, - attributes={ - "sentry.op": OP.QUEUE_PROCESS, - "sentry.origin": CeleryIntegration.origin, - }, - ) + span_ctx = nullcontext() + if get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=task.name, + attributes={ + "sentry.op": OP.QUEUE_PROCESS, + "sentry.origin": CeleryIntegration.origin, + }, + ) else: - span = sentry_sdk.start_span( + span_ctx = sentry_sdk.start_span( op=OP.QUEUE_PROCESS, name=task.name, origin=CeleryIntegration.origin, ) - with span: - if isinstance(span, StreamedSpan): - set_on_span = span.set_attribute - else: - set_on_span = span.set_data - - _set_messaging_destination_name(task, span) - - latency = None - with capture_internal_exceptions(): - if ( - task.request.headers is not None - and "sentry-task-enqueued-time" in task.request.headers - ): - latency = _now_seconds_since_epoch() - task.request.headers.pop( - "sentry-task-enqueued-time" - ) - - if latency is not None: - latency *= 1000 # milliseconds - set_on_span(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) - - with capture_internal_exceptions(): - set_on_span(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id) - - with capture_internal_exceptions(): - set_on_span( - SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, task.request.retries - ) - - with capture_internal_exceptions(): - with task.app.connection() as conn: + with span_ctx as span: + if span is not None: + if isinstance(span, StreamedSpan): + set_on_span = span.set_attribute + else: + set_on_span = span.set_data + + _set_messaging_destination_name(task, span) + + latency = None + with capture_internal_exceptions(): + if ( + task.request.headers is not None + and "sentry-task-enqueued-time" in task.request.headers + ): + latency = ( + _now_seconds_since_epoch() + - task.request.headers.pop("sentry-task-enqueued-time") + ) + + if latency is not None: + latency *= 1000 # milliseconds + set_on_span(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) + + with capture_internal_exceptions(): + set_on_span(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id) + + with capture_internal_exceptions(): set_on_span( - SPANDATA.MESSAGING_SYSTEM, - conn.transport.driver_type, + SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, + task.request.retries, ) + with capture_internal_exceptions(): + with task.app.connection() as conn: + set_on_span( + SPANDATA.MESSAGING_SYSTEM, + conn.transport.driver_type, + ) + return f(*args, **kwargs) except Exception: exc_info = sys.exc_info() 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..382a67066f 100644 --- a/sentry_sdk/integrations/cohere.py +++ b/sentry_sdk/integrations/cohere.py @@ -17,7 +17,12 @@ 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, + nullcontext, + reraise, +) try: from cohere import ( @@ -154,6 +159,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,13 +257,16 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": ) if is_span_streaming_enabled: - span_ctx = sentry_sdk.traces.start_span( - name="Cohere Embedding Creation", - attributes={ - "sentry.op": consts.OP.COHERE_EMBEDDINGS_CREATE, - "sentry.origin": CohereIntegration.origin, - }, - ) + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="Cohere Embedding Creation", + attributes={ + "sentry.op": consts.OP.COHERE_EMBEDDINGS_CREATE, + "sentry.origin": CohereIntegration.origin, + }, + ) + else: + span_ctx = nullcontext() else: span_ctx = get_start_span_function()( op=consts.OP.COHERE_EMBEDDINGS_CREATE, @@ -265,22 +275,23 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": ) with span_ctx as span: - if "texts" in kwargs and ( - should_send_default_pii() and integration.include_prompts - ): - if isinstance(kwargs["texts"], str): - set_data_normalized(span, SPANDATA.AI_TEXTS, [kwargs["texts"]]) - elif ( - isinstance(kwargs["texts"], list) - and len(kwargs["texts"]) > 0 - and isinstance(kwargs["texts"][0], str) + if span is not None: + if "texts" in kwargs and ( + should_send_default_pii() and integration.include_prompts ): - set_data_normalized( - span, SPANDATA.AI_INPUT_MESSAGES, kwargs["texts"] - ) + if isinstance(kwargs["texts"], str): + set_data_normalized(span, SPANDATA.AI_TEXTS, [kwargs["texts"]]) + elif ( + isinstance(kwargs["texts"], list) + and len(kwargs["texts"]) > 0 + and isinstance(kwargs["texts"][0], str) + ): + set_data_normalized( + span, SPANDATA.AI_INPUT_MESSAGES, kwargs["texts"] + ) - if "model" in kwargs: - set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"]) + if "model" in kwargs: + set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"]) try: res = f(*args, **kwargs) except Exception as e: @@ -288,7 +299,7 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": with capture_internal_exceptions(): _capture_exception(e) reraise(*exc_info) - if ( + if span is not None and ( hasattr(res, "meta") and hasattr(res.meta, "billed_units") and hasattr(res.meta.billed_units, "input_tokens") diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index 361b60079d..304d05f5e6 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -31,6 +31,7 @@ ensure_integration_enabled, event_from_exception, logger, + nullcontext, transaction_from_function, walk_exception_chain, ) @@ -724,14 +725,18 @@ def connect(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name="connect", - attributes={ - "sentry.op": OP.DB, - "sentry.origin": DjangoIntegration.origin_db, - }, - ) as span: - _set_db_data(span, self) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="connect", + attributes={ + "sentry.op": OP.DB, + "sentry.origin": DjangoIntegration.origin_db, + }, + ) + with span_ctx as span: + if span is not None: + _set_db_data(span, self) return real_connect(self) else: with sentry_sdk.start_span( @@ -750,14 +755,18 @@ def _commit(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=SPANNAME.DB_COMMIT, - attributes={ - "sentry.op": OP.DB, - "sentry.origin": DjangoIntegration.origin_db, - }, - ) as span: - _set_db_data(span, self, SPANNAME.DB_COMMIT) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=SPANNAME.DB_COMMIT, + attributes={ + "sentry.op": OP.DB, + "sentry.origin": DjangoIntegration.origin_db, + }, + ) + with span_ctx as span: + if span is not None: + _set_db_data(span, self, SPANNAME.DB_COMMIT) return real_commit(self) else: with sentry_sdk.start_span( @@ -776,14 +785,18 @@ def _rollback(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=SPANNAME.DB_ROLLBACK, - attributes={ - "sentry.op": OP.DB, - "sentry.origin": DjangoIntegration.origin_db, - }, - ) as span: - _set_db_data(span, self, SPANNAME.DB_ROLLBACK) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=SPANNAME.DB_ROLLBACK, + attributes={ + "sentry.op": OP.DB, + "sentry.origin": DjangoIntegration.origin_db, + }, + ) + with span_ctx as span: + if span is not None: + _set_db_data(span, self, SPANNAME.DB_ROLLBACK) return real_rollback(self) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 43faffb5be..5d60cc6480 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -22,6 +22,7 @@ from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, + nullcontext, ) if TYPE_CHECKING: @@ -191,13 +192,16 @@ async def sentry_wrapped_callback( return await callback(request, *args, **kwargs) if span_streaming: - with sentry_sdk.traces.start_span( - name=request.resolver_match.view_name, - attributes={ - "sentry.op": OP.VIEW_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ): + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=request.resolver_match.view_name, + attributes={ + "sentry.op": OP.VIEW_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx: return await callback(request, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/caching.py b/sentry_sdk/integrations/django/caching.py index faf1803c11..e06bdd7e29 100644 --- a/sentry_sdk/integrations/django/caching.py +++ b/sentry_sdk/integrations/django/caching.py @@ -12,6 +12,7 @@ from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, + nullcontext, ) if TYPE_CHECKING: @@ -61,56 +62,60 @@ def _instrument_call( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=description, - attributes={ - "sentry.op": op, - "sentry.origin": DjangoIntegration.origin, - }, - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=description, + attributes={ + "sentry.op": op, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx as span: value = original_method(*args, **kwargs) - with capture_internal_exceptions(): - if address is not None: - span.set_attribute(SPANDATA.NETWORK_PEER_ADDRESS, address) - - if port is not None: - span.set_attribute(SPANDATA.NETWORK_PEER_PORT, port) - - key = _get_safe_key(method_name, args, kwargs) - if key is not None: - span.set_attribute(SPANDATA.CACHE_KEY, key) - - item_size = None - if is_get_many_method: - if value != {}: - item_size = len(str(value)) - span.set_attribute(SPANDATA.CACHE_HIT, True) - else: - span.set_attribute(SPANDATA.CACHE_HIT, False) - elif is_get_method: - default_value = None - if len(args) >= 2: - default_value = args[1] - elif "default" in kwargs: - default_value = kwargs["default"] - - if value != default_value: - item_size = len(str(value)) - span.set_attribute(SPANDATA.CACHE_HIT, True) - else: - span.set_attribute(SPANDATA.CACHE_HIT, False) - else: # TODO: We don't handle `get_or_set` which we should - arg_count = len(args) - if arg_count >= 2: - # 'set' command - item_size = len(str(args[1])) - elif arg_count == 1: - # 'set_many' command - item_size = len(str(args[0])) - - if item_size is not None: - span.set_attribute(SPANDATA.CACHE_ITEM_SIZE, item_size) + if span is not None: + with capture_internal_exceptions(): + if address is not None: + span.set_attribute(SPANDATA.NETWORK_PEER_ADDRESS, address) + + if port is not None: + span.set_attribute(SPANDATA.NETWORK_PEER_PORT, port) + + key = _get_safe_key(method_name, args, kwargs) + if key is not None: + span.set_attribute(SPANDATA.CACHE_KEY, key) + + item_size = None + if is_get_many_method: + if value != {}: + item_size = len(str(value)) + span.set_attribute(SPANDATA.CACHE_HIT, True) + else: + span.set_attribute(SPANDATA.CACHE_HIT, False) + elif is_get_method: + default_value = None + if len(args) >= 2: + default_value = args[1] + elif "default" in kwargs: + default_value = kwargs["default"] + + if value != default_value: + item_size = len(str(value)) + span.set_attribute(SPANDATA.CACHE_HIT, True) + else: + span.set_attribute(SPANDATA.CACHE_HIT, False) + else: # TODO: We don't handle `get_or_set` which we should + arg_count = len(args) + if arg_count >= 2: + # 'set' command + item_size = len(str(args[1])) + elif arg_count == 1: + # 'set_many' command + item_size = len(str(args[0])) + + if item_size is not None: + span.set_attribute(SPANDATA.CACHE_ITEM_SIZE, item_size) return value else: 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..90aec10a34 100644 --- a/sentry_sdk/integrations/django/signals_handlers.py +++ b/sentry_sdk/integrations/django/signals_handlers.py @@ -7,6 +7,7 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.django import DJANGO_VERSION from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext if TYPE_CHECKING: from collections.abc import Callable @@ -68,14 +69,17 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any": sentry_sdk.get_client().options ) if span_streaming: - with sentry_sdk.traces.start_span( - name=signal_name, - attributes={ - "sentry.op": OP.EVENT_DJANGO, - "sentry.origin": DjangoIntegration.origin, - SPANDATA.CODE_FUNCTION_NAME: signal_name, - }, - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=signal_name, + attributes={ + "sentry.op": OP.EVENT_DJANGO, + "sentry.origin": DjangoIntegration.origin, + SPANDATA.CODE_FUNCTION_NAME: signal_name, + }, + ) + with span_ctx: 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..61e8852acb 100644 --- a/sentry_sdk/integrations/django/tasks.py +++ b/sentry_sdk/integrations/django/tasks.py @@ -3,7 +3,7 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import qualname_from_function +from sentry_sdk.utils import nullcontext, qualname_from_function try: # django.tasks were added in Django 6.0 @@ -35,13 +35,16 @@ def _sentry_enqueue(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=name, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_DJANGO, - "sentry.origin": DjangoIntegration.origin, - }, - ): + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=name, + attributes={ + "sentry.op": OP.QUEUE_SUBMIT_DJANGO, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx: return old_task_enqueue(self, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index 5ab89d4a74..0f7fb1759c 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -8,7 +8,7 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import ensure_integration_enabled +from sentry_sdk.utils import ensure_integration_enabled, nullcontext if TYPE_CHECKING: from typing import Any, Dict, Iterator, Optional, Tuple @@ -64,13 +64,16 @@ def patch_templates() -> None: def rendered_content(self: "SimpleTemplateResponse") -> str: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - 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: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=_get_template_name_description(self.template_name), + attributes={ + "sentry.op": OP.TEMPLATE_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx: return real_rendered_content.fget(self) else: with sentry_sdk.start_span( @@ -109,13 +112,16 @@ def render( span_streaming = has_span_streaming_enabled(client.options) if span_streaming: - 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: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=_get_template_name_description(template_name), + attributes={ + "sentry.op": OP.TEMPLATE_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx: 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..da12abf31b 100644 --- a/sentry_sdk/integrations/django/views.py +++ b/sentry_sdk/integrations/django/views.py @@ -5,6 +5,7 @@ from sentry_sdk.consts import OP from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext if TYPE_CHECKING: from typing import Any @@ -34,13 +35,16 @@ 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: - with sentry_sdk.traces.start_span( - name="serialize response", - attributes={ - "sentry.op": OP.VIEW_RESPONSE_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ): + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="serialize response", + attributes={ + "sentry.op": OP.VIEW_RESPONSE_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx: return old_render(self) else: with sentry_sdk.start_span( @@ -108,13 +112,16 @@ def sentry_wrapped_callback(request: "Any", *args: "Any", **kwargs: "Any") -> "A return callback(request, *args, **kwargs) if span_streaming: - with sentry_sdk.traces.start_span( - name=request.resolver_match.view_name, - attributes={ - "sentry.op": OP.VIEW_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ): + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=request.resolver_match.view_name, + attributes={ + "sentry.op": OP.VIEW_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ) + with span_ctx: return callback(request, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/google_genai/__init__.py b/sentry_sdk/integrations/google_genai/__init__.py index 45652c3f71..dfe7521d96 100644 --- a/sentry_sdk/integrations/google_genai/__init__.py +++ b/sentry_sdk/integrations/google_genai/__init__.py @@ -14,6 +14,7 @@ from sentry_sdk.traces import SpanStatus, StreamedSpan from sentry_sdk.tracing import SPANSTATUS from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext try: from google.genai.models import AsyncModels, Models @@ -76,17 +77,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 +103,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 +120,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 +162,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 +188,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 +205,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,28 +243,34 @@ 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): - with 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, - }, - ) as chat_span: - set_span_data_for_request( - chat_span, integration, model_name, contents, kwargs + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = 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, + }, ) + with span_ctx as chat_span: + if chat_span is not None: + set_span_data_for_request( + chat_span, integration, model_name, contents, kwargs + ) try: response = f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - chat_span.status = SpanStatus.ERROR + if chat_span is not None: + chat_span.status = SpanStatus.ERROR raise - set_span_data_for_response(chat_span, integration, response) + if chat_span is not None: + set_span_data_for_response(chat_span, integration, response) return response else: @@ -290,27 +313,33 @@ async def new_async_generate_content( model, contents, model_name = prepare_generate_content_args(args, kwargs) if has_span_streaming_enabled(client.options): - with 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, - }, - ) as chat_span: - set_span_data_for_request( - chat_span, integration, model_name, contents, kwargs + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = 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, + }, ) + with span_ctx as chat_span: + if chat_span is not None: + set_span_data_for_request( + chat_span, integration, model_name, contents, kwargs + ) try: response = await f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - chat_span.status = SpanStatus.ERROR + if chat_span is not None: + chat_span.status = SpanStatus.ERROR raise - set_span_data_for_response(chat_span, integration, response) + if chat_span is not None: + set_span_data_for_response(chat_span, integration, response) return response else: @@ -350,26 +379,32 @@ 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): - with sentry_sdk.traces.start_span( - name=f"embeddings {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, - SPANDATA.GEN_AI_REQUEST_MODEL: model_name, - }, - ) as span: - set_span_data_for_embed_request(span, integration, contents, kwargs) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=f"embeddings {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, + SPANDATA.GEN_AI_REQUEST_MODEL: model_name, + }, + ) + with span_ctx as span: + if span is not None: + set_span_data_for_embed_request(span, integration, contents, kwargs) try: response = f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - span.status = SpanStatus.ERROR + if span is not None: + span.status = SpanStatus.ERROR raise - set_span_data_for_embed_response(span, integration, response) + if span is not None: + set_span_data_for_embed_response(span, integration, response) return response else: @@ -410,26 +445,32 @@ async def new_async_embed_content( model_name, contents = prepare_embed_content_args(args, kwargs) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=f"embeddings {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, - SPANDATA.GEN_AI_REQUEST_MODEL: model_name, - }, - ) as span: - set_span_data_for_embed_request(span, integration, contents, kwargs) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=f"embeddings {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, + SPANDATA.GEN_AI_REQUEST_MODEL: model_name, + }, + ) + with span_ctx as span: + if span is not None: + set_span_data_for_embed_request(span, integration, contents, kwargs) try: response = await f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - span.status = SpanStatus.ERROR + if span is not None: + span.status = SpanStatus.ERROR raise - set_span_data_for_embed_response(span, integration, response) + if span is not None: + set_span_data_for_embed_response(span, integration, response) return response else: 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..836847a819 100644 --- a/sentry_sdk/integrations/grpc/aio/client.py +++ b/sentry_sdk/integrations/grpc/aio/client.py @@ -5,6 +5,7 @@ from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext try: from google.protobuf.message import Message @@ -52,14 +53,17 @@ async def intercept_unary_unary( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name="unary unary call to %s" % method.decode(), - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method.decode(), - }, - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="unary unary call to %s" % method.decode(), + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method.decode(), + }, + ) + with span_ctx as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details @@ -67,8 +71,11 @@ async def intercept_unary_unary( ) response = await continuation(client_call_details, request) - status_code = await response.code() - span.set_attribute(SPANDATA.RPC_RESPONSE_STATUS_CODE, status_code.name) + if span is not None: + status_code = await response.code() + span.set_attribute( + SPANDATA.RPC_RESPONSE_STATUS_CODE, status_code.name + ) return response else: @@ -107,14 +114,17 @@ async def intercept_unary_stream( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name="unary stream call to %s" % method.decode(), - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method.decode(), - }, - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="unary stream call to %s" % method.decode(), + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method.decode(), + }, + ) + with span_ctx as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details diff --git a/sentry_sdk/integrations/grpc/client.py b/sentry_sdk/integrations/grpc/client.py index 5384a0a78f..c1d9df6eeb 100644 --- a/sentry_sdk/integrations/grpc/client.py +++ b/sentry_sdk/integrations/grpc/client.py @@ -5,6 +5,7 @@ from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext if TYPE_CHECKING: from typing import Any, Callable, Iterable, Iterator, Union @@ -33,14 +34,17 @@ def intercept_unary_unary( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name="unary unary call to %s" % method, - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method, - }, - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="unary unary call to %s" % method, + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method, + }, + ) + with span_ctx as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details @@ -48,9 +52,10 @@ def intercept_unary_unary( ) response = continuation(client_call_details, request) - span.set_attribute( - SPANDATA.RPC_RESPONSE_STATUS_CODE, response.code().name - ) + if span is not None: + span.set_attribute( + SPANDATA.RPC_RESPONSE_STATUS_CODE, response.code().name + ) return response else: @@ -84,14 +89,17 @@ def intercept_unary_stream( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) response: "UnaryStreamCall" if span_streaming: - with sentry_sdk.traces.start_span( - name="unary stream call to %s" % method, - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method, - }, - ) as span: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="unary stream call to %s" % method, + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method, + }, + ) + with span_ctx as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details 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..9bde220376 100644 --- a/sentry_sdk/integrations/huey.py +++ b/sentry_sdk/integrations/huey.py @@ -20,6 +20,7 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, + nullcontext, reraise, ) @@ -81,14 +82,17 @@ def _sentry_enqueue( span_ctx = None if is_span_streaming_enabled: - span_ctx = sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_HUEY, - "sentry.origin": HueyIntegration.origin, - SPANDATA.MESSAGING_DESTINATION_NAME: self.name, - }, - ) + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=span_name, + attributes={ + "sentry.op": OP.QUEUE_SUBMIT_HUEY, + "sentry.origin": HueyIntegration.origin, + SPANDATA.MESSAGING_DESTINATION_NAME: self.name, + }, + ) + else: + span_ctx = nullcontext() else: span_ctx = sentry_sdk.start_span( op=OP.QUEUE_SUBMIT_HUEY, 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..4ecacd0d78 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -24,7 +24,7 @@ has_span_streaming_enabled, should_truncate_gen_ai_input, ) -from sentry_sdk.utils import capture_internal_exceptions, logger +from sentry_sdk.utils import capture_internal_exceptions, logger, nullcontext if TYPE_CHECKING: from typing import ( @@ -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,53 +1029,60 @@ 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): - with 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: False, - }, - ) as span: - if run_name: - span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = 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: False, + }, + ) + with span_ctx as span: + if span is not None: + if run_name: + span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name) - _set_tools_on_span(span, tools) + _set_tools_on_span(span, tools) # Run the agent result = f(self, *args, **kwargs) - input = result.get("input") - 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, + if span is not None: + input = result.get("input") + 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, + ) - output = result.get("output") - if ( - output is not None - and should_send_default_pii() - and integration.include_prompts - ): - set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output) + output = result.get("output") + if ( + output is not None + and should_send_default_pii() + and integration.include_prompts + ): + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output) return result else: @@ -1135,17 +1151,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 +1181,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,29 +1309,38 @@ 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): - with sentry_sdk.traces.start_span( - name=f"embeddings {model_name}" if model_name else "embeddings", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": LangchainIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - }, - ) as span: - if model_name: - span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) - - # Capture input if PII is allowed - if ( - should_send_default_pii() - and integration.include_prompts - and len(args) > 0 - ): - input_data = args[0] - # 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_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=f"embeddings {model_name}" if model_name else "embeddings", + attributes={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": LangchainIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + }, + ) + with span_ctx as span: + if span is not None: + if model_name: + span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) + + # Capture input if PII is allowed + if ( + should_send_default_pii() + and integration.include_prompts + and len(args) > 0 + ): + input_data = args[0] + # 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, + ) result = f(self, *args, **kwargs) return result @@ -1357,29 +1388,38 @@ 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): - with sentry_sdk.traces.start_span( - name=f"embeddings {model_name}" if model_name else "embeddings", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": LangchainIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - }, - ) as span: - if model_name: - span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) - - # Capture input if PII is allowed - if ( - should_send_default_pii() - and integration.include_prompts - and len(args) > 0 - ): - input_data = args[0] - # 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_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=f"embeddings {model_name}" if model_name else "embeddings", + attributes={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": LangchainIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + }, + ) + with span_ctx as span: + if span is not None: + if model_name: + span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) + + # Capture input if PII is allowed + if ( + should_send_default_pii() + and integration.include_prompts + and len(args) > 0 + ): + input_data = args[0] + # 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, + ) result = await f(self, *args, **kwargs) return result diff --git a/sentry_sdk/integrations/langgraph.py b/sentry_sdk/integrations/langgraph.py index 3d3856a913..747bf94af0 100644 --- a/sentry_sdk/integrations/langgraph.py +++ b/sentry_sdk/integrations/langgraph.py @@ -19,7 +19,7 @@ has_span_streaming_enabled, should_truncate_gen_ai_input, ) -from sentry_sdk.utils import safe_serialize +from sentry_sdk.utils import nullcontext, safe_serialize try: from langgraph.errors import GraphBubbleUp @@ -174,22 +174,26 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": LanggraphIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) as span: - if graph_name: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=span_name, + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": LanggraphIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) + with span_ctx as span: + if span is not None and graph_name: span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, graph_name) span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name) # Store input messages to later compare with output input_messages = None if ( - len(args) > 0 + span is not None + and len(args) > 0 and should_send_default_pii() and integration.include_prompts ): @@ -218,7 +222,8 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": result = f(self, *args, **kwargs) - _set_response_attributes(span, input_messages, result, integration) + if span is not None: + _set_response_attributes(span, input_messages, result, integration) return result else: @@ -286,21 +291,25 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": LanggraphIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) as span: - if graph_name: + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=span_name, + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": LanggraphIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) + with span_ctx as span: + if span is not None and graph_name: span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, graph_name) span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name) input_messages = None if ( - len(args) > 0 + span is not None + and len(args) > 0 and should_send_default_pii() and integration.include_prompts ): @@ -329,7 +338,8 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": result = await f(self, *args, **kwargs) - _set_response_attributes(span, input_messages, result, integration) + if span is not None: + _set_response_attributes(span, input_messages, result, integration) return result 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..179bb0b7ee 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -16,6 +16,7 @@ from sentry_sdk.utils import ( ensure_integration_enabled, event_from_exception, + nullcontext, transaction_from_function, ) @@ -164,14 +165,20 @@ async def _create_span_call( middleware_name = self.__class__.__name__ if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=middleware_name, - attributes={ - "sentry.op": OP.MIDDLEWARE_LITESTAR, - "sentry.origin": LitestarIntegration.origin, - }, - ) as middleware_span: - middleware_span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=middleware_name, + attributes={ + "sentry.op": OP.MIDDLEWARE_LITESTAR, + "sentry.origin": LitestarIntegration.origin, + }, + ) + with span_ctx as middleware_span: + if middleware_span is not None: + middleware_span.set_attribute( + SPANDATA.MIDDLEWARE_NAME, middleware_name + ) # Creating spans for the "receive" callback async def _sentry_receive( @@ -179,14 +186,20 @@ async def _sentry_receive( ) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]": if client.get_integration(LitestarIntegration) is None: return await receive(*args, **kwargs) - with sentry_sdk.traces.start_span( - name=getattr(receive, "__qualname__", str(receive)), - attributes={ - "sentry.op": OP.MIDDLEWARE_LITESTAR_RECEIVE, - "sentry.origin": LitestarIntegration.origin, - }, - ) as span: - span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) + recv_span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + recv_span_ctx = sentry_sdk.traces.start_span( + name=getattr(receive, "__qualname__", str(receive)), + attributes={ + "sentry.op": OP.MIDDLEWARE_LITESTAR_RECEIVE, + "sentry.origin": LitestarIntegration.origin, + }, + ) + with recv_span_ctx as span: + if span is not None: + span.set_attribute( + SPANDATA.MIDDLEWARE_NAME, middleware_name + ) return await receive(*args, **kwargs) receive_name = getattr(receive, "__name__", str(receive)) @@ -197,14 +210,20 @@ async def _sentry_receive( async def _sentry_send(message: "Message") -> None: if client.get_integration(LitestarIntegration) is None: return await send(message) - with sentry_sdk.traces.start_span( - name=getattr(send, "__qualname__", str(send)), - attributes={ - "sentry.op": OP.MIDDLEWARE_LITESTAR_SEND, - "sentry.origin": LitestarIntegration.origin, - }, - ) as span: - span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) + send_span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + send_span_ctx = sentry_sdk.traces.start_span( + name=getattr(send, "__qualname__", str(send)), + attributes={ + "sentry.op": OP.MIDDLEWARE_LITESTAR_SEND, + "sentry.origin": LitestarIntegration.origin, + }, + ) + with send_span_ctx as span: + if span is not None: + span.set_attribute( + SPANDATA.MIDDLEWARE_NAME, middleware_name + ) return await send(message) send_name = getattr(send, "__name__", str(send)) 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..eaa9e97b31 100644 --- a/sentry_sdk/integrations/openai_agents/spans/handoff.py +++ b/sentry_sdk/integrations/openai_agents/spans/handoff.py @@ -3,6 +3,7 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext from ..consts import SPAN_ORIGIN @@ -15,18 +16,22 @@ def handoff_span( ) -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=f"handoff from {from_agent.name} to {to_agent_name}", - attributes={ - "sentry.op": OP.GEN_AI_HANDOFF, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "handoff", - }, - ) as span: - # Add conversation ID from agent - conv_id = getattr(from_agent, "_sentry_conversation_id", None) - if conv_id: - span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=f"handoff from {from_agent.name} to {to_agent_name}", + attributes={ + "sentry.op": OP.GEN_AI_HANDOFF, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "handoff", + }, + ) + with span_ctx as span: + if span is not None: + # Add conversation ID from agent + conv_id = getattr(from_agent, "_sentry_conversation_id", None) + if conv_id: + span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) else: with sentry_sdk.start_span( op=OP.GEN_AI_HANDOFF, 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..6f70697a6b 100644 --- a/sentry_sdk/integrations/ray.py +++ b/sentry_sdk/integrations/ray.py @@ -11,6 +11,7 @@ from sentry_sdk.utils import ( event_from_exception, logger, + nullcontext, package_version, qualname_from_function, reraise, @@ -156,15 +157,18 @@ def _remote_method_with_header_propagation( ) if span_streaming: function_name = qualname_from_function(user_f) - with sentry_sdk.traces.start_span( - name="unknown Ray task" - if function_name is None - else function_name, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_RAY, - "sentry.origin": RayIntegration.origin, - }, - ): + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name="unknown Ray task" + if function_name is None + else function_name, + attributes={ + "sentry.op": OP.QUEUE_SUBMIT_RAY, + "sentry.origin": RayIntegration.origin, + }, + ) + with span_ctx: tracing = { k: v for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers() 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..3c9b7d42de 100644 --- a/sentry_sdk/integrations/socket.py +++ b/sentry_sdk/integrations/socket.py @@ -5,6 +5,7 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import Integration from sentry_sdk.tracing_utils import has_span_streaming_enabled +from sentry_sdk.utils import nullcontext if MYPY: from socket import AddressFamily, SocketKind @@ -57,16 +58,20 @@ def create_connection( return real_create_connection(address, timeout, source_address) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=_get_span_description(address[0], address[1]), - attributes={ - "sentry.op": OP.SOCKET_CONNECTION, - "sentry.origin": SocketIntegration.origin, - }, - ) as span: - if address[0] is not None: - span.set_attribute(SPANDATA.SERVER_ADDRESS, address[0]) - span.set_attribute(SPANDATA.SERVER_PORT, address[1]) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=_get_span_description(address[0], address[1]), + attributes={ + "sentry.op": OP.SOCKET_CONNECTION, + "sentry.origin": SocketIntegration.origin, + }, + ) + with span_ctx as span: + if span is not None: + if address[0] is not None: + span.set_attribute(SPANDATA.SERVER_ADDRESS, address[0]) + span.set_attribute(SPANDATA.SERVER_PORT, address[1]) return real_create_connection( address=address, timeout=timeout, source_address=source_address @@ -105,27 +110,31 @@ def getaddrinfo( return real_getaddrinfo(host, port, family, type, proto, flags) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( - name=_get_span_description(host, port), - attributes={ - "sentry.op": OP.SOCKET_DNS, - "sentry.origin": SocketIntegration.origin, - }, - ) as span: - if isinstance(host, str): - span.set_attribute(SPANDATA.SERVER_ADDRESS, host) - elif isinstance(host, bytes): - span.set_attribute( - SPANDATA.SERVER_ADDRESS, host.decode(errors="replace") - ) - - if isinstance(port, int): - span.set_attribute(SPANDATA.SERVER_PORT, port) - elif port is not None: - try: - span.set_attribute(SPANDATA.SERVER_PORT, int(port)) - except (ValueError, TypeError): - pass + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=_get_span_description(host, port), + attributes={ + "sentry.op": OP.SOCKET_DNS, + "sentry.origin": SocketIntegration.origin, + }, + ) + with span_ctx as span: + if span is not None: + if isinstance(host, str): + span.set_attribute(SPANDATA.SERVER_ADDRESS, host) + elif isinstance(host, bytes): + span.set_attribute( + SPANDATA.SERVER_ADDRESS, host.decode(errors="replace") + ) + + if isinstance(port, int): + span.set_attribute(SPANDATA.SERVER_PORT, port) + elif port is not None: + try: + span.set_attribute(SPANDATA.SERVER_PORT, int(port)) + except (ValueError, TypeError): + pass return real_getaddrinfo(host, port, family, type, proto, flags) else: 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..09a0a1d071 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -23,6 +23,7 @@ ensure_integration_enabled, is_sentry_url, logger, + nullcontext, parse_url, safe_repr, ) @@ -114,6 +115,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 +307,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,14 +360,18 @@ def sentry_patched_popen_wait( ) -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=OP.SUBPROCESS_WAIT, - attributes={ - "sentry.op": OP.SUBPROCESS_WAIT, - "sentry.origin": "auto.subprocess.stdlib.subprocess", - }, - ) as span: - span.set_attribute(SPANDATA.PROCESS_PID, self.pid) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=OP.SUBPROCESS_WAIT, + attributes={ + "sentry.op": OP.SUBPROCESS_WAIT, + "sentry.origin": "auto.subprocess.stdlib.subprocess", + }, + ) + with span_ctx as span: + if span is not None: + span.set_attribute(SPANDATA.PROCESS_PID, self.pid) return old_popen_wait(self, *a, **kw) else: with sentry_sdk.start_span( @@ -380,14 +391,18 @@ def sentry_patched_popen_communicate( ) -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - with sentry_sdk.traces.start_span( - name=OP.SUBPROCESS_COMMUNICATE, - attributes={ - "sentry.op": OP.SUBPROCESS_COMMUNICATE, - "sentry.origin": "auto.subprocess.stdlib.subprocess", - }, - ) as span: - span.set_attribute(SPANDATA.PROCESS_PID, self.pid) + span_ctx = nullcontext() + if sentry_sdk.traces.get_current_span() is not None: + span_ctx = sentry_sdk.traces.start_span( + name=OP.SUBPROCESS_COMMUNICATE, + attributes={ + "sentry.op": OP.SUBPROCESS_COMMUNICATE, + "sentry.origin": "auto.subprocess.stdlib.subprocess", + }, + ) + with span_ctx as span: + if span is not None: + span.set_attribute(SPANDATA.PROCESS_PID, self.pid) return old_popen_communicate(self, *a, **kw) else: with sentry_sdk.start_span( 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={ From df31a30a5b806bcf49bb00a08bdd242fba4f4729 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 13 Jul 2026 15:37:26 +0200 Subject: [PATCH 3/4] ref: Use early returns instead of nullcontext where possible Replace nullcontext + if-span-is-not-None patterns with early returns when the with block ends with a return. This removes unnecessary nesting and nullcontext imports from 23 files. The only remaining nullcontext usage is in pyreqwest.py where the function is a generator context manager that must yield. Co-Authored-By: Claude Opus 4.6 --- sentry_sdk/integrations/aiomysql.py | 47 ++-- sentry_sdk/integrations/arq.py | 21 +- sentry_sdk/integrations/asyncpg.py | 58 +++-- sentry_sdk/integrations/celery/__init__.py | 91 ++++---- sentry_sdk/integrations/cohere.py | 51 ++--- sentry_sdk/integrations/django/__init__.py | 67 +++--- sentry_sdk/integrations/django/asgi.py | 20 +- sentry_sdk/integrations/django/caching.py | 103 ++++----- .../integrations/django/signals_handlers.py | 22 +- sentry_sdk/integrations/django/tasks.py | 21 +- sentry_sdk/integrations/django/templates.py | 40 ++-- sentry_sdk/integrations/django/views.py | 39 ++-- .../integrations/google_genai/__init__.py | 145 ++++++------ sentry_sdk/integrations/grpc/aio/client.py | 56 ++--- sentry_sdk/integrations/grpc/client.py | 56 +++-- sentry_sdk/integrations/huey.py | 36 +-- sentry_sdk/integrations/langchain.py | 214 +++++++++--------- sentry_sdk/integrations/langgraph.py | 62 +++-- sentry_sdk/integrations/litestar.py | 73 +++--- .../openai_agents/spans/handoff.py | 32 ++- sentry_sdk/integrations/ray.py | 39 ++-- sentry_sdk/integrations/socket.py | 77 +++---- sentry_sdk/integrations/stdlib.py | 45 ++-- 23 files changed, 679 insertions(+), 736 deletions(-) diff --git a/sentry_sdk/integrations/aiomysql.py b/sentry_sdk/integrations/aiomysql.py index 3f1d6728c9..c3348566a2 100644 --- a/sentry_sdk/integrations/aiomysql.py +++ b/sentry_sdk/integrations/aiomysql.py @@ -14,7 +14,6 @@ ) from sentry_sdk.utils import ( capture_internal_exceptions, - nullcontext, parse_version, ) @@ -180,36 +179,34 @@ async def _inner(self: "Connection") -> T: "sentry.origin": AioMySQLIntegration.origin, } | breadcrumb_data - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="connect", attributes=span_attributes - ) - with span_ctx as span: + if sentry_sdk.traces.get_current_span() is None: + return await f(self) + + with sentry_sdk.traces.start_span( + name="connect", attributes=span_attributes + ): 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/arq.py b/sentry_sdk/integrations/arq.py index e5c3e69ba5..011c5e51bf 100644 --- a/sentry_sdk/integrations/arq.py +++ b/sentry_sdk/integrations/arq.py @@ -14,7 +14,6 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, - nullcontext, parse_version, reraise, ) @@ -80,16 +79,16 @@ async def _sentry_enqueue_job( return await old_enqueue_job(self, function, *args, **kwargs) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=function, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_ARQ, - "sentry.origin": ArqIntegration.origin, - }, - ) - with span_ctx: + 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={ + "sentry.op": OP.QUEUE_SUBMIT_ARQ, + "sentry.origin": ArqIntegration.origin, + }, + ): return await old_enqueue_job(self, function, *args, **kwargs) with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/asyncpg.py b/sentry_sdk/integrations/asyncpg.py index 474c0d17ca..f79d1507d8 100644 --- a/sentry_sdk/integrations/asyncpg.py +++ b/sentry_sdk/integrations/asyncpg.py @@ -16,7 +16,6 @@ ) from sentry_sdk.utils import ( capture_internal_exceptions, - nullcontext, parse_version, ) @@ -236,42 +235,39 @@ async def _inner(*args: "Any", **kwargs: "Any") -> "T": except IndexError: pass - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="connect", attributes=span_attributes - ) - with span_ctx as span: + 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 + ): with capture_internal_exceptions(): sentry_sdk.add_breadcrumb( message="connect", category="query", data=span_attributes ) - res = await f(*args, **kwargs) + return 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") - - 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/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index 71e2aa9825..9a0618d0b5 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -23,7 +23,6 @@ SENSITIVE_DATA_SUBSTITUTE, capture_internal_exceptions, event_from_exception, - nullcontext, reraise, ) @@ -420,17 +419,17 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": span_streaming = has_span_streaming_enabled(client.options) try: - span_ctx: "Any" + if span_streaming and get_current_span() is None: + return f(*args, **kwargs) + if span_streaming: - span_ctx = nullcontext() - if get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=task.name, - attributes={ - "sentry.op": OP.QUEUE_PROCESS, - "sentry.origin": CeleryIntegration.origin, - }, - ) + span_ctx = sentry_sdk.traces.start_span( + name=task.name, + attributes={ + "sentry.op": OP.QUEUE_PROCESS, + "sentry.origin": CeleryIntegration.origin, + }, + ) else: span_ctx = sentry_sdk.start_span( op=OP.QUEUE_PROCESS, @@ -439,44 +438,42 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": ) with span_ctx as span: - if span is not None: - if isinstance(span, StreamedSpan): - set_on_span = span.set_attribute - else: - set_on_span = span.set_data - - _set_messaging_destination_name(task, span) - - latency = None - with capture_internal_exceptions(): - if ( - task.request.headers is not None - and "sentry-task-enqueued-time" in task.request.headers - ): - latency = ( - _now_seconds_since_epoch() - - task.request.headers.pop("sentry-task-enqueued-time") - ) - - if latency is not None: - latency *= 1000 # milliseconds - set_on_span(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) - - with capture_internal_exceptions(): - set_on_span(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id) - - with capture_internal_exceptions(): - set_on_span( - SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, - task.request.retries, + if isinstance(span, StreamedSpan): + set_on_span = span.set_attribute + else: + set_on_span = span.set_data + + _set_messaging_destination_name(task, span) + + latency = None + with capture_internal_exceptions(): + if ( + task.request.headers is not None + and "sentry-task-enqueued-time" in task.request.headers + ): + latency = _now_seconds_since_epoch() - task.request.headers.pop( + "sentry-task-enqueued-time" ) - with capture_internal_exceptions(): - with task.app.connection() as conn: - set_on_span( - SPANDATA.MESSAGING_SYSTEM, - conn.transport.driver_type, - ) + if latency is not None: + latency *= 1000 # milliseconds + set_on_span(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) + + with capture_internal_exceptions(): + set_on_span(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id) + + with capture_internal_exceptions(): + set_on_span( + SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, + task.request.retries, + ) + + with capture_internal_exceptions(): + with task.app.connection() as conn: + set_on_span( + SPANDATA.MESSAGING_SYSTEM, + conn.transport.driver_type, + ) return f(*args, **kwargs) except Exception: diff --git a/sentry_sdk/integrations/cohere.py b/sentry_sdk/integrations/cohere.py index 382a67066f..c805c601e3 100644 --- a/sentry_sdk/integrations/cohere.py +++ b/sentry_sdk/integrations/cohere.py @@ -20,7 +20,6 @@ from sentry_sdk.utils import ( capture_internal_exceptions, event_from_exception, - nullcontext, reraise, ) @@ -257,16 +256,15 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": ) if is_span_streaming_enabled: - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="Cohere Embedding Creation", - attributes={ - "sentry.op": consts.OP.COHERE_EMBEDDINGS_CREATE, - "sentry.origin": CohereIntegration.origin, - }, - ) - else: - span_ctx = nullcontext() + 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={ + "sentry.op": consts.OP.COHERE_EMBEDDINGS_CREATE, + "sentry.origin": CohereIntegration.origin, + }, + ) else: span_ctx = get_start_span_function()( op=consts.OP.COHERE_EMBEDDINGS_CREATE, @@ -275,23 +273,22 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": ) with span_ctx as span: - if span is not None: - if "texts" in kwargs and ( - should_send_default_pii() and integration.include_prompts + if "texts" in kwargs and ( + should_send_default_pii() and integration.include_prompts + ): + if isinstance(kwargs["texts"], str): + set_data_normalized(span, SPANDATA.AI_TEXTS, [kwargs["texts"]]) + elif ( + isinstance(kwargs["texts"], list) + and len(kwargs["texts"]) > 0 + and isinstance(kwargs["texts"][0], str) ): - if isinstance(kwargs["texts"], str): - set_data_normalized(span, SPANDATA.AI_TEXTS, [kwargs["texts"]]) - elif ( - isinstance(kwargs["texts"], list) - and len(kwargs["texts"]) > 0 - and isinstance(kwargs["texts"][0], str) - ): - set_data_normalized( - span, SPANDATA.AI_INPUT_MESSAGES, kwargs["texts"] - ) + set_data_normalized( + span, SPANDATA.AI_INPUT_MESSAGES, kwargs["texts"] + ) - if "model" in kwargs: - set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"]) + if "model" in kwargs: + set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"]) try: res = f(*args, **kwargs) except Exception as e: @@ -299,7 +296,7 @@ def new_embed(*args: "Any", **kwargs: "Any") -> "Any": with capture_internal_exceptions(): _capture_exception(e) reraise(*exc_info) - if span is not None and ( + if ( hasattr(res, "meta") and hasattr(res.meta, "billed_units") and hasattr(res.meta.billed_units, "input_tokens") diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index 304d05f5e6..2c561164e6 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -31,7 +31,6 @@ ensure_integration_enabled, event_from_exception, logger, - nullcontext, transaction_from_function, walk_exception_chain, ) @@ -725,18 +724,16 @@ def connect(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="connect", - attributes={ - "sentry.op": OP.DB, - "sentry.origin": DjangoIntegration.origin_db, - }, - ) - with span_ctx as span: - if span is not None: - _set_db_data(span, self) + if sentry_sdk.traces.get_current_span() is None: + return real_connect(self) + with sentry_sdk.traces.start_span( + name="connect", + attributes={ + "sentry.op": OP.DB, + "sentry.origin": DjangoIntegration.origin_db, + }, + ) as span: + _set_db_data(span, self) return real_connect(self) else: with sentry_sdk.start_span( @@ -755,18 +752,16 @@ def _commit(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=SPANNAME.DB_COMMIT, - attributes={ - "sentry.op": OP.DB, - "sentry.origin": DjangoIntegration.origin_db, - }, - ) - with span_ctx as span: - if span is not None: - _set_db_data(span, self, SPANNAME.DB_COMMIT) + if sentry_sdk.traces.get_current_span() is None: + return real_commit(self) + with sentry_sdk.traces.start_span( + name=SPANNAME.DB_COMMIT, + attributes={ + "sentry.op": OP.DB, + "sentry.origin": DjangoIntegration.origin_db, + }, + ) as span: + _set_db_data(span, self, SPANNAME.DB_COMMIT) return real_commit(self) else: with sentry_sdk.start_span( @@ -785,18 +780,16 @@ def _rollback(self: "BaseDatabaseWrapper") -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=SPANNAME.DB_ROLLBACK, - attributes={ - "sentry.op": OP.DB, - "sentry.origin": DjangoIntegration.origin_db, - }, - ) - with span_ctx as span: - if span is not None: - _set_db_data(span, self, SPANNAME.DB_ROLLBACK) + if sentry_sdk.traces.get_current_span() is None: + return real_rollback(self) + with sentry_sdk.traces.start_span( + name=SPANNAME.DB_ROLLBACK, + attributes={ + "sentry.op": OP.DB, + "sentry.origin": DjangoIntegration.origin_db, + }, + ) as span: + _set_db_data(span, self, SPANNAME.DB_ROLLBACK) return real_rollback(self) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 5d60cc6480..785fa2af34 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -22,7 +22,6 @@ from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, - nullcontext, ) if TYPE_CHECKING: @@ -192,16 +191,15 @@ async def sentry_wrapped_callback( return await callback(request, *args, **kwargs) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=request.resolver_match.view_name, - attributes={ - "sentry.op": OP.VIEW_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx: + 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={ + "sentry.op": OP.VIEW_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ): return await callback(request, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/caching.py b/sentry_sdk/integrations/django/caching.py index e06bdd7e29..2cfc0cd2e1 100644 --- a/sentry_sdk/integrations/django/caching.py +++ b/sentry_sdk/integrations/django/caching.py @@ -12,7 +12,6 @@ from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, - nullcontext, ) if TYPE_CHECKING: @@ -62,60 +61,58 @@ def _instrument_call( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=description, - attributes={ - "sentry.op": op, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx as span: + if sentry_sdk.traces.get_current_span() is None: + return original_method(*args, **kwargs) + with sentry_sdk.traces.start_span( + name=description, + attributes={ + "sentry.op": op, + "sentry.origin": DjangoIntegration.origin, + }, + ) as span: value = original_method(*args, **kwargs) - if span is not None: - with capture_internal_exceptions(): - if address is not None: - span.set_attribute(SPANDATA.NETWORK_PEER_ADDRESS, address) - - if port is not None: - span.set_attribute(SPANDATA.NETWORK_PEER_PORT, port) - - key = _get_safe_key(method_name, args, kwargs) - if key is not None: - span.set_attribute(SPANDATA.CACHE_KEY, key) - - item_size = None - if is_get_many_method: - if value != {}: - item_size = len(str(value)) - span.set_attribute(SPANDATA.CACHE_HIT, True) - else: - span.set_attribute(SPANDATA.CACHE_HIT, False) - elif is_get_method: - default_value = None - if len(args) >= 2: - default_value = args[1] - elif "default" in kwargs: - default_value = kwargs["default"] - - if value != default_value: - item_size = len(str(value)) - span.set_attribute(SPANDATA.CACHE_HIT, True) - else: - span.set_attribute(SPANDATA.CACHE_HIT, False) - else: # TODO: We don't handle `get_or_set` which we should - arg_count = len(args) - if arg_count >= 2: - # 'set' command - item_size = len(str(args[1])) - elif arg_count == 1: - # 'set_many' command - item_size = len(str(args[0])) - - if item_size is not None: - span.set_attribute(SPANDATA.CACHE_ITEM_SIZE, item_size) + with capture_internal_exceptions(): + if address is not None: + span.set_attribute(SPANDATA.NETWORK_PEER_ADDRESS, address) + + if port is not None: + span.set_attribute(SPANDATA.NETWORK_PEER_PORT, port) + + key = _get_safe_key(method_name, args, kwargs) + if key is not None: + span.set_attribute(SPANDATA.CACHE_KEY, key) + + item_size = None + if is_get_many_method: + if value != {}: + item_size = len(str(value)) + span.set_attribute(SPANDATA.CACHE_HIT, True) + else: + span.set_attribute(SPANDATA.CACHE_HIT, False) + elif is_get_method: + default_value = None + if len(args) >= 2: + default_value = args[1] + elif "default" in kwargs: + default_value = kwargs["default"] + + if value != default_value: + item_size = len(str(value)) + span.set_attribute(SPANDATA.CACHE_HIT, True) + else: + span.set_attribute(SPANDATA.CACHE_HIT, False) + else: # TODO: We don't handle `get_or_set` which we should + arg_count = len(args) + if arg_count >= 2: + # 'set' command + item_size = len(str(args[1])) + elif arg_count == 1: + # 'set_many' command + item_size = len(str(args[0])) + + if item_size is not None: + span.set_attribute(SPANDATA.CACHE_ITEM_SIZE, item_size) return value else: diff --git a/sentry_sdk/integrations/django/signals_handlers.py b/sentry_sdk/integrations/django/signals_handlers.py index 90aec10a34..711e74b441 100644 --- a/sentry_sdk/integrations/django/signals_handlers.py +++ b/sentry_sdk/integrations/django/signals_handlers.py @@ -7,7 +7,6 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.django import DJANGO_VERSION from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext if TYPE_CHECKING: from collections.abc import Callable @@ -69,17 +68,16 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any": sentry_sdk.get_client().options ) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=signal_name, - attributes={ - "sentry.op": OP.EVENT_DJANGO, - "sentry.origin": DjangoIntegration.origin, - SPANDATA.CODE_FUNCTION_NAME: signal_name, - }, - ) - with span_ctx: + if sentry_sdk.traces.get_current_span() is None: + return receiver(*args, **kwargs) + with sentry_sdk.traces.start_span( + name=signal_name, + attributes={ + "sentry.op": OP.EVENT_DJANGO, + "sentry.origin": DjangoIntegration.origin, + SPANDATA.CODE_FUNCTION_NAME: signal_name, + }, + ): 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 61e8852acb..303040d042 100644 --- a/sentry_sdk/integrations/django/tasks.py +++ b/sentry_sdk/integrations/django/tasks.py @@ -3,7 +3,7 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext, qualname_from_function +from sentry_sdk.utils import qualname_from_function try: # django.tasks were added in Django 6.0 @@ -35,16 +35,15 @@ def _sentry_enqueue(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=name, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_DJANGO, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx: + 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={ + "sentry.op": OP.QUEUE_SUBMIT_DJANGO, + "sentry.origin": DjangoIntegration.origin, + }, + ): return old_task_enqueue(self, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index 0f7fb1759c..1c0986dd9e 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -8,7 +8,7 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import ensure_integration_enabled, nullcontext +from sentry_sdk.utils import ensure_integration_enabled if TYPE_CHECKING: from typing import Any, Dict, Iterator, Optional, Tuple @@ -64,16 +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: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=_get_template_name_description(self.template_name), - attributes={ - "sentry.op": OP.TEMPLATE_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx: + 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, + }, + ): return real_rendered_content.fget(self) else: with sentry_sdk.start_span( @@ -112,16 +111,15 @@ def render( span_streaming = has_span_streaming_enabled(client.options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=_get_template_name_description(template_name), - attributes={ - "sentry.op": OP.TEMPLATE_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx: + 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, + }, + ): 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 da12abf31b..b24e4df45b 100644 --- a/sentry_sdk/integrations/django/views.py +++ b/sentry_sdk/integrations/django/views.py @@ -5,7 +5,6 @@ from sentry_sdk.consts import OP from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext if TYPE_CHECKING: from typing import Any @@ -35,16 +34,15 @@ 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: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="serialize response", - attributes={ - "sentry.op": OP.VIEW_RESPONSE_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx: + if sentry_sdk.traces.get_current_span() is None: + return old_render(self) + with sentry_sdk.traces.start_span( + name="serialize response", + attributes={ + "sentry.op": OP.VIEW_RESPONSE_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ): return old_render(self) else: with sentry_sdk.start_span( @@ -112,16 +110,15 @@ def sentry_wrapped_callback(request: "Any", *args: "Any", **kwargs: "Any") -> "A return callback(request, *args, **kwargs) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=request.resolver_match.view_name, - attributes={ - "sentry.op": OP.VIEW_RENDER, - "sentry.origin": DjangoIntegration.origin, - }, - ) - with span_ctx: + 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={ + "sentry.op": OP.VIEW_RENDER, + "sentry.origin": DjangoIntegration.origin, + }, + ): return callback(request, *args, **kwargs) else: with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/google_genai/__init__.py b/sentry_sdk/integrations/google_genai/__init__.py index dfe7521d96..ed46553d81 100644 --- a/sentry_sdk/integrations/google_genai/__init__.py +++ b/sentry_sdk/integrations/google_genai/__init__.py @@ -14,7 +14,6 @@ from sentry_sdk.traces import SpanStatus, StreamedSpan from sentry_sdk.tracing import SPANSTATUS from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext try: from google.genai.models import AsyncModels, Models @@ -243,34 +242,31 @@ 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): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = 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, - }, + 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={ + "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, + }, + ) as chat_span: + set_span_data_for_request( + chat_span, integration, model_name, contents, kwargs ) - with span_ctx as chat_span: - if chat_span is not None: - set_span_data_for_request( - chat_span, integration, model_name, contents, kwargs - ) try: response = f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - if chat_span is not None: - chat_span.status = SpanStatus.ERROR + chat_span.status = SpanStatus.ERROR raise - if chat_span is not None: - set_span_data_for_response(chat_span, integration, response) + set_span_data_for_response(chat_span, integration, response) return response else: @@ -313,33 +309,30 @@ async def new_async_generate_content( model, contents, model_name = prepare_generate_content_args(args, kwargs) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = 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, - }, + 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={ + "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, + }, + ) as chat_span: + set_span_data_for_request( + chat_span, integration, model_name, contents, kwargs ) - with span_ctx as chat_span: - if chat_span is not None: - set_span_data_for_request( - chat_span, integration, model_name, contents, kwargs - ) try: response = await f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - if chat_span is not None: - chat_span.status = SpanStatus.ERROR + chat_span.status = SpanStatus.ERROR raise - if chat_span is not None: - set_span_data_for_response(chat_span, integration, response) + set_span_data_for_response(chat_span, integration, response) return response else: @@ -379,32 +372,29 @@ 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): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=f"embeddings {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, - SPANDATA.GEN_AI_REQUEST_MODEL: model_name, - }, - ) - with span_ctx as span: - if span is not None: - set_span_data_for_embed_request(span, integration, contents, kwargs) + 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={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, + SPANDATA.GEN_AI_REQUEST_MODEL: model_name, + }, + ) as span: + set_span_data_for_embed_request(span, integration, contents, kwargs) try: response = f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - if span is not None: - span.status = SpanStatus.ERROR + span.status = SpanStatus.ERROR raise - if span is not None: - set_span_data_for_embed_response(span, integration, response) + set_span_data_for_embed_response(span, integration, response) return response else: @@ -445,32 +435,29 @@ async def new_async_embed_content( model_name, contents = prepare_embed_content_args(args, kwargs) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=f"embeddings {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, - SPANDATA.GEN_AI_REQUEST_MODEL: model_name, - }, - ) - with span_ctx as span: - if span is not None: - set_span_data_for_embed_request(span, integration, contents, kwargs) + 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={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + SPANDATA.GEN_AI_SYSTEM: GEN_AI_SYSTEM, + SPANDATA.GEN_AI_REQUEST_MODEL: model_name, + }, + ) as span: + set_span_data_for_embed_request(span, integration, contents, kwargs) try: response = await f(self, *args, **kwargs) except Exception as exc: _capture_exception(exc) - if span is not None: - span.status = SpanStatus.ERROR + span.status = SpanStatus.ERROR raise - if span is not None: - set_span_data_for_embed_response(span, integration, response) + set_span_data_for_embed_response(span, integration, response) return response else: diff --git a/sentry_sdk/integrations/grpc/aio/client.py b/sentry_sdk/integrations/grpc/aio/client.py index 836847a819..97e9d828c0 100644 --- a/sentry_sdk/integrations/grpc/aio/client.py +++ b/sentry_sdk/integrations/grpc/aio/client.py @@ -5,7 +5,6 @@ from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext try: from google.protobuf.message import Message @@ -53,17 +52,21 @@ async def intercept_unary_unary( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="unary unary call to %s" % method.decode(), - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method.decode(), - }, + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) ) - with span_ctx as span: + return await continuation(client_call_details, request) + with sentry_sdk.traces.start_span( + name="unary unary call to %s" % method.decode(), + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method.decode(), + }, + ) as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details @@ -71,11 +74,8 @@ async def intercept_unary_unary( ) response = await continuation(client_call_details, request) - if span is not None: - status_code = await response.code() - span.set_attribute( - SPANDATA.RPC_RESPONSE_STATUS_CODE, status_code.name - ) + status_code = await response.code() + span.set_attribute(SPANDATA.RPC_RESPONSE_STATUS_CODE, status_code.name) return response else: @@ -114,17 +114,21 @@ async def intercept_unary_stream( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="unary stream call to %s" % method.decode(), - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method.decode(), - }, + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) ) - with span_ctx as span: + return await continuation(client_call_details, request) + with sentry_sdk.traces.start_span( + name="unary stream call to %s" % method.decode(), + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method.decode(), + }, + ) as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details diff --git a/sentry_sdk/integrations/grpc/client.py b/sentry_sdk/integrations/grpc/client.py index c1d9df6eeb..fe9954445e 100644 --- a/sentry_sdk/integrations/grpc/client.py +++ b/sentry_sdk/integrations/grpc/client.py @@ -5,7 +5,6 @@ from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext if TYPE_CHECKING: from typing import Any, Callable, Iterable, Iterator, Union @@ -34,17 +33,21 @@ def intercept_unary_unary( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="unary unary call to %s" % method, - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method, - }, + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) ) - with span_ctx as span: + return continuation(client_call_details, request) + with sentry_sdk.traces.start_span( + name="unary unary call to %s" % method, + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method, + }, + ) as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details @@ -52,10 +55,9 @@ def intercept_unary_unary( ) response = continuation(client_call_details, request) - if span is not None: - span.set_attribute( - SPANDATA.RPC_RESPONSE_STATUS_CODE, response.code().name - ) + span.set_attribute( + SPANDATA.RPC_RESPONSE_STATUS_CODE, response.code().name + ) return response else: @@ -89,17 +91,21 @@ def intercept_unary_stream( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) response: "UnaryStreamCall" if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="unary stream call to %s" % method, - attributes={ - "sentry.op": OP.GRPC_CLIENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.RPC_METHOD: method, - }, + if sentry_sdk.traces.get_current_span() is None: + client_call_details = ( + self._update_client_call_details_metadata_from_scope( + client_call_details + ) ) - with span_ctx as span: + return continuation(client_call_details, request) + with sentry_sdk.traces.start_span( + name="unary stream call to %s" % method, + attributes={ + "sentry.op": OP.GRPC_CLIENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.RPC_METHOD: method, + }, + ) as span: client_call_details = ( self._update_client_call_details_metadata_from_scope( client_call_details diff --git a/sentry_sdk/integrations/huey.py b/sentry_sdk/integrations/huey.py index 9bde220376..70814d4b7f 100644 --- a/sentry_sdk/integrations/huey.py +++ b/sentry_sdk/integrations/huey.py @@ -20,7 +20,6 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, - nullcontext, reraise, ) @@ -80,19 +79,27 @@ 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: - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_HUEY, - "sentry.origin": HueyIntegration.origin, - SPANDATA.MESSAGING_DESTINATION_NAME: self.name, - }, - ) - else: - span_ctx = nullcontext() + span_ctx = sentry_sdk.traces.start_span( + name=span_name, + attributes={ + "sentry.op": OP.QUEUE_SUBMIT_HUEY, + "sentry.origin": HueyIntegration.origin, + SPANDATA.MESSAGING_DESTINATION_NAME: self.name, + }, + ) else: span_ctx = sentry_sdk.start_span( op=OP.QUEUE_SUBMIT_HUEY, @@ -101,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/langchain.py b/sentry_sdk/integrations/langchain.py index 4ecacd0d78..9188f1a43e 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -24,7 +24,7 @@ has_span_streaming_enabled, should_truncate_gen_ai_input, ) -from sentry_sdk.utils import capture_internal_exceptions, logger, nullcontext +from sentry_sdk.utils import capture_internal_exceptions, logger if TYPE_CHECKING: from typing import ( @@ -1029,60 +1029,56 @@ 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): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = 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: False, - }, - ) - with span_ctx as span: - if span is not None: - if run_name: - span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name) + 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={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": LangchainIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + SPANDATA.GEN_AI_RESPONSE_STREAMING: False, + }, + ) as span: + if run_name: + span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name) - _set_tools_on_span(span, tools) + _set_tools_on_span(span, tools) # Run the agent result = f(self, *args, **kwargs) - if span is not None: - input = result.get("input") - 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 + input = result.get("input") + 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, ) - if messages_data is not None: - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, - unpack=False, - ) - output = result.get("output") - if ( - output is not None - and should_send_default_pii() - and integration.include_prompts - ): - set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output) + output = result.get("output") + if ( + output is not None + and should_send_default_pii() + and integration.include_prompts + ): + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output) return result else: @@ -1309,38 +1305,35 @@ 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): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=f"embeddings {model_name}" if model_name else "embeddings", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": LangchainIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - }, - ) - with span_ctx as span: - if span is not None: - if model_name: - span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) - - # Capture input if PII is allowed - if ( - should_send_default_pii() - and integration.include_prompts - and len(args) > 0 - ): - input_data = args[0] - # 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, - ) + 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={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": LangchainIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + }, + ) as span: + if model_name: + span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) + + # Capture input if PII is allowed + if ( + should_send_default_pii() + and integration.include_prompts + and len(args) > 0 + ): + input_data = args[0] + # 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, + ) result = f(self, *args, **kwargs) return result @@ -1388,38 +1381,35 @@ 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): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=f"embeddings {model_name}" if model_name else "embeddings", - attributes={ - "sentry.op": OP.GEN_AI_EMBEDDINGS, - "sentry.origin": LangchainIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", - }, - ) - with span_ctx as span: - if span is not None: - if model_name: - span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) - - # Capture input if PII is allowed - if ( - should_send_default_pii() - and integration.include_prompts - and len(args) > 0 - ): - input_data = args[0] - # 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, - ) + 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={ + "sentry.op": OP.GEN_AI_EMBEDDINGS, + "sentry.origin": LangchainIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "embeddings", + }, + ) as span: + if model_name: + span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) + + # Capture input if PII is allowed + if ( + should_send_default_pii() + and integration.include_prompts + and len(args) > 0 + ): + input_data = args[0] + # 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, + ) result = await f(self, *args, **kwargs) return result diff --git a/sentry_sdk/integrations/langgraph.py b/sentry_sdk/integrations/langgraph.py index 747bf94af0..2fc6444bde 100644 --- a/sentry_sdk/integrations/langgraph.py +++ b/sentry_sdk/integrations/langgraph.py @@ -19,7 +19,7 @@ has_span_streaming_enabled, should_truncate_gen_ai_input, ) -from sentry_sdk.utils import nullcontext, safe_serialize +from sentry_sdk.utils import safe_serialize try: from langgraph.errors import GraphBubbleUp @@ -174,26 +174,25 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": LanggraphIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) - with span_ctx as span: - if span is not None and graph_name: + if sentry_sdk.traces.get_current_span() is None: + return f(self, *args, **kwargs) + + with sentry_sdk.traces.start_span( + name=span_name, + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": LanggraphIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) as span: + if graph_name: span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, graph_name) span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name) # Store input messages to later compare with output input_messages = None if ( - span is not None - and len(args) > 0 + len(args) > 0 and should_send_default_pii() and integration.include_prompts ): @@ -222,8 +221,7 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": result = f(self, *args, **kwargs) - if span is not None: - _set_response_attributes(span, input_messages, result, integration) + _set_response_attributes(span, input_messages, result, integration) return result else: @@ -291,25 +289,24 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=span_name, - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": LanggraphIntegration.origin, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) - with span_ctx as span: - if span is not None and graph_name: + 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={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": LanggraphIntegration.origin, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) as span: + if graph_name: span.set_attribute(SPANDATA.GEN_AI_PIPELINE_NAME, graph_name) span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, graph_name) input_messages = None if ( - span is not None - and len(args) > 0 + len(args) > 0 and should_send_default_pii() and integration.include_prompts ): @@ -338,8 +335,7 @@ async def new_ainvoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": result = await f(self, *args, **kwargs) - if span is not None: - _set_response_attributes(span, input_messages, result, integration) + _set_response_attributes(span, input_messages, result, integration) return result diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 179bb0b7ee..cb7aa87ae9 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -16,7 +16,6 @@ from sentry_sdk.utils import ( ensure_integration_enabled, event_from_exception, - nullcontext, transaction_from_function, ) @@ -165,20 +164,16 @@ async def _create_span_call( middleware_name = self.__class__.__name__ if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=middleware_name, - attributes={ - "sentry.op": OP.MIDDLEWARE_LITESTAR, - "sentry.origin": LitestarIntegration.origin, - }, - ) - with span_ctx as middleware_span: - if middleware_span is not None: - middleware_span.set_attribute( - SPANDATA.MIDDLEWARE_NAME, middleware_name - ) + 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={ + "sentry.op": OP.MIDDLEWARE_LITESTAR, + "sentry.origin": LitestarIntegration.origin, + }, + ) as middleware_span: + middleware_span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) # Creating spans for the "receive" callback async def _sentry_receive( @@ -186,20 +181,16 @@ async def _sentry_receive( ) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]": if client.get_integration(LitestarIntegration) is None: return await receive(*args, **kwargs) - recv_span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - recv_span_ctx = sentry_sdk.traces.start_span( - name=getattr(receive, "__qualname__", str(receive)), - attributes={ - "sentry.op": OP.MIDDLEWARE_LITESTAR_RECEIVE, - "sentry.origin": LitestarIntegration.origin, - }, - ) - with recv_span_ctx as span: - if span is not None: - span.set_attribute( - SPANDATA.MIDDLEWARE_NAME, middleware_name - ) + 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={ + "sentry.op": OP.MIDDLEWARE_LITESTAR_RECEIVE, + "sentry.origin": LitestarIntegration.origin, + }, + ) as span: + span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) return await receive(*args, **kwargs) receive_name = getattr(receive, "__name__", str(receive)) @@ -210,20 +201,16 @@ async def _sentry_receive( async def _sentry_send(message: "Message") -> None: if client.get_integration(LitestarIntegration) is None: return await send(message) - send_span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - send_span_ctx = sentry_sdk.traces.start_span( - name=getattr(send, "__qualname__", str(send)), - attributes={ - "sentry.op": OP.MIDDLEWARE_LITESTAR_SEND, - "sentry.origin": LitestarIntegration.origin, - }, - ) - with send_span_ctx as span: - if span is not None: - span.set_attribute( - SPANDATA.MIDDLEWARE_NAME, middleware_name - ) + 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={ + "sentry.op": OP.MIDDLEWARE_LITESTAR_SEND, + "sentry.origin": LitestarIntegration.origin, + }, + ) as span: + span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) return await send(message) send_name = getattr(send, "__name__", str(send)) diff --git a/sentry_sdk/integrations/openai_agents/spans/handoff.py b/sentry_sdk/integrations/openai_agents/spans/handoff.py index eaa9e97b31..4d1c987de5 100644 --- a/sentry_sdk/integrations/openai_agents/spans/handoff.py +++ b/sentry_sdk/integrations/openai_agents/spans/handoff.py @@ -3,7 +3,6 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext from ..consts import SPAN_ORIGIN @@ -16,22 +15,21 @@ def handoff_span( ) -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=f"handoff from {from_agent.name} to {to_agent_name}", - attributes={ - "sentry.op": OP.GEN_AI_HANDOFF, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "handoff", - }, - ) - with span_ctx as span: - if span is not None: - # Add conversation ID from agent - conv_id = getattr(from_agent, "_sentry_conversation_id", None) - if conv_id: - span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) + 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={ + "sentry.op": OP.GEN_AI_HANDOFF, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "handoff", + }, + ) as span: + # Add conversation ID from agent + conv_id = getattr(from_agent, "_sentry_conversation_id", None) + if conv_id: + span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) else: with sentry_sdk.start_span( op=OP.GEN_AI_HANDOFF, diff --git a/sentry_sdk/integrations/ray.py b/sentry_sdk/integrations/ray.py index 6f70697a6b..f48bd59791 100644 --- a/sentry_sdk/integrations/ray.py +++ b/sentry_sdk/integrations/ray.py @@ -11,7 +11,6 @@ from sentry_sdk.utils import ( event_from_exception, logger, - nullcontext, package_version, qualname_from_function, reraise, @@ -157,18 +156,32 @@ def _remote_method_with_header_propagation( ) if span_streaming: function_name = qualname_from_function(user_f) - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name="unknown Ray task" - if function_name is None - else function_name, - attributes={ - "sentry.op": OP.QUEUE_SUBMIT_RAY, - "sentry.origin": RayIntegration.origin, - }, - ) - with span_ctx: + + 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 + else function_name, + attributes={ + "sentry.op": OP.QUEUE_SUBMIT_RAY, + "sentry.origin": RayIntegration.origin, + }, + ): tracing = { k: v for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers() diff --git a/sentry_sdk/integrations/socket.py b/sentry_sdk/integrations/socket.py index 3c9b7d42de..c719f14227 100644 --- a/sentry_sdk/integrations/socket.py +++ b/sentry_sdk/integrations/socket.py @@ -5,7 +5,6 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import Integration from sentry_sdk.tracing_utils import has_span_streaming_enabled -from sentry_sdk.utils import nullcontext if MYPY: from socket import AddressFamily, SocketKind @@ -58,20 +57,19 @@ def create_connection( return real_create_connection(address, timeout, source_address) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=_get_span_description(address[0], address[1]), - attributes={ - "sentry.op": OP.SOCKET_CONNECTION, - "sentry.origin": SocketIntegration.origin, - }, - ) - with span_ctx as span: - if span is not None: - if address[0] is not None: - span.set_attribute(SPANDATA.SERVER_ADDRESS, address[0]) - span.set_attribute(SPANDATA.SERVER_PORT, address[1]) + 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={ + "sentry.op": OP.SOCKET_CONNECTION, + "sentry.origin": SocketIntegration.origin, + }, + ) as span: + if address[0] is not None: + span.set_attribute(SPANDATA.SERVER_ADDRESS, address[0]) + span.set_attribute(SPANDATA.SERVER_PORT, address[1]) return real_create_connection( address=address, timeout=timeout, source_address=source_address @@ -110,31 +108,30 @@ def getaddrinfo( return real_getaddrinfo(host, port, family, type, proto, flags) if has_span_streaming_enabled(client.options): - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=_get_span_description(host, port), - attributes={ - "sentry.op": OP.SOCKET_DNS, - "sentry.origin": SocketIntegration.origin, - }, - ) - with span_ctx as span: - if span is not None: - if isinstance(host, str): - span.set_attribute(SPANDATA.SERVER_ADDRESS, host) - elif isinstance(host, bytes): - span.set_attribute( - SPANDATA.SERVER_ADDRESS, host.decode(errors="replace") - ) - - if isinstance(port, int): - span.set_attribute(SPANDATA.SERVER_PORT, port) - elif port is not None: - try: - span.set_attribute(SPANDATA.SERVER_PORT, int(port)) - except (ValueError, TypeError): - pass + 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={ + "sentry.op": OP.SOCKET_DNS, + "sentry.origin": SocketIntegration.origin, + }, + ) as span: + if isinstance(host, str): + span.set_attribute(SPANDATA.SERVER_ADDRESS, host) + elif isinstance(host, bytes): + span.set_attribute( + SPANDATA.SERVER_ADDRESS, host.decode(errors="replace") + ) + + if isinstance(port, int): + span.set_attribute(SPANDATA.SERVER_PORT, port) + elif port is not None: + try: + span.set_attribute(SPANDATA.SERVER_PORT, int(port)) + except (ValueError, TypeError): + pass return real_getaddrinfo(host, port, family, type, proto, flags) else: diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index 09a0a1d071..1cb1b62a5f 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -23,7 +23,6 @@ ensure_integration_enabled, is_sentry_url, logger, - nullcontext, parse_url, safe_repr, ) @@ -360,18 +359,16 @@ def sentry_patched_popen_wait( ) -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=OP.SUBPROCESS_WAIT, - attributes={ - "sentry.op": OP.SUBPROCESS_WAIT, - "sentry.origin": "auto.subprocess.stdlib.subprocess", - }, - ) - with span_ctx as span: - if span is not None: - span.set_attribute(SPANDATA.PROCESS_PID, self.pid) + 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={ + "sentry.op": OP.SUBPROCESS_WAIT, + "sentry.origin": "auto.subprocess.stdlib.subprocess", + }, + ) as span: + span.set_attribute(SPANDATA.PROCESS_PID, self.pid) return old_popen_wait(self, *a, **kw) else: with sentry_sdk.start_span( @@ -391,18 +388,16 @@ def sentry_patched_popen_communicate( ) -> "Any": span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span_ctx = nullcontext() - if sentry_sdk.traces.get_current_span() is not None: - span_ctx = sentry_sdk.traces.start_span( - name=OP.SUBPROCESS_COMMUNICATE, - attributes={ - "sentry.op": OP.SUBPROCESS_COMMUNICATE, - "sentry.origin": "auto.subprocess.stdlib.subprocess", - }, - ) - with span_ctx as span: - if span is not None: - span.set_attribute(SPANDATA.PROCESS_PID, self.pid) + 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={ + "sentry.op": OP.SUBPROCESS_COMMUNICATE, + "sentry.origin": "auto.subprocess.stdlib.subprocess", + }, + ) as span: + span.set_attribute(SPANDATA.PROCESS_PID, self.pid) return old_popen_communicate(self, *a, **kw) else: with sentry_sdk.start_span( From 8f7552683610cadaf4cb34c835f3e9aacd5bca88 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 13 Jul 2026 15:49:16 +0200 Subject: [PATCH 4/4] ref: Revert unnecessary span variable rename in celery Co-Authored-By: Claude Opus 4.6 --- sentry_sdk/integrations/celery/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index 9a0618d0b5..a4abac4618 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -422,8 +422,9 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": if span_streaming and get_current_span() is None: return f(*args, **kwargs) + span: "Union[Span, StreamedSpan]" if span_streaming: - span_ctx = sentry_sdk.traces.start_span( + span = sentry_sdk.traces.start_span( name=task.name, attributes={ "sentry.op": OP.QUEUE_PROCESS, @@ -431,13 +432,13 @@ def _inner(*args: "Any", **kwargs: "Any") -> "Any": }, ) else: - span_ctx = sentry_sdk.start_span( + span = sentry_sdk.start_span( op=OP.QUEUE_PROCESS, name=task.name, origin=CeleryIntegration.origin, ) - with span_ctx as span: + with span: if isinstance(span, StreamedSpan): set_on_span = span.set_attribute else: