From f2686b9dbb00e39736ca00342c9cf691e21596b7 Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Thu, 7 Nov 2024 15:04:17 +0100 Subject: [PATCH] Add `only_if_parent` option to POTelSpan and use it in integrations If this option is on, we will only create a new underlying otel span if there's an active valid parent, otherwise we will just return an invalid `NonRecordingSpan` (`INVALID_SPAN`). All internal integration child `start_span` calls have been modified so that now we will only create spans if there is an active root span (transaction) active. --- sentry_sdk/ai/monitoring.py | 8 ++- sentry_sdk/integrations/aiohttp.py | 1 + sentry_sdk/integrations/anthropic.py | 1 + sentry_sdk/integrations/arq.py | 5 +- sentry_sdk/integrations/asyncio.py | 1 + sentry_sdk/integrations/asyncpg.py | 1 + sentry_sdk/integrations/boto3.py | 1 + sentry_sdk/integrations/celery/__init__.py | 1 + sentry_sdk/integrations/clickhouse_driver.py | 1 + sentry_sdk/integrations/cohere.py | 2 + sentry_sdk/integrations/django/__init__.py | 1 + sentry_sdk/integrations/django/asgi.py | 1 + sentry_sdk/integrations/django/caching.py | 1 + sentry_sdk/integrations/django/middleware.py | 1 + .../integrations/django/signals_handlers.py | 1 + sentry_sdk/integrations/django/templates.py | 2 + sentry_sdk/integrations/django/views.py | 2 + sentry_sdk/integrations/graphene.py | 4 +- sentry_sdk/integrations/grpc/aio/client.py | 2 + sentry_sdk/integrations/grpc/client.py | 2 + sentry_sdk/integrations/httpx.py | 2 + sentry_sdk/integrations/huey.py | 1 + sentry_sdk/integrations/huggingface_hub.py | 1 + sentry_sdk/integrations/langchain.py | 4 +- sentry_sdk/integrations/litestar.py | 3 ++ sentry_sdk/integrations/openai.py | 2 + sentry_sdk/integrations/pymongo.py | 1 + sentry_sdk/integrations/ray.py | 1 + .../integrations/redis/_async_common.py | 3 ++ sentry_sdk/integrations/redis/_sync_common.py | 1 + sentry_sdk/integrations/socket.py | 2 + sentry_sdk/integrations/starlette.py | 3 ++ sentry_sdk/integrations/starlite.py | 3 ++ sentry_sdk/integrations/stdlib.py | 4 ++ sentry_sdk/integrations/strawberry.py | 1 + sentry_sdk/tracing.py | 50 ++++++++++++------- 36 files changed, 98 insertions(+), 23 deletions(-) diff --git a/sentry_sdk/ai/monitoring.py b/sentry_sdk/ai/monitoring.py index 860833b8f5..e149ebe7df 100644 --- a/sentry_sdk/ai/monitoring.py +++ b/sentry_sdk/ai/monitoring.py @@ -33,7 +33,9 @@ def sync_wrapped(*args, **kwargs): curr_pipeline = _ai_pipeline_name.get() op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline") - with start_span(name=description, op=op, **span_kwargs) as span: + with start_span( + name=description, op=op, only_if_parent=True, **span_kwargs + ) as span: for k, v in kwargs.pop("sentry_tags", {}).items(): span.set_tag(k, v) for k, v in kwargs.pop("sentry_data", {}).items(): @@ -62,7 +64,9 @@ async def async_wrapped(*args, **kwargs): curr_pipeline = _ai_pipeline_name.get() op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline") - with start_span(name=description, op=op, **span_kwargs) as span: + with start_span( + name=description, op=op, only_if_parent=True, **span_kwargs + ) as span: for k, v in kwargs.pop("sentry_tags", {}).items(): span.set_tag(k, v) for k, v in kwargs.pop("sentry_data", {}).items(): diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 36c1d807d2..0928c14c8b 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -228,6 +228,7 @@ async def on_request_start(session, trace_config_ctx, params): name="%s %s" % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), origin=AioHttpIntegration.origin, + only_if_parent=True, ) data = { diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 87e69a3113..d128ad7d25 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -151,6 +151,7 @@ def _sentry_patched_create_common(f, *args, **kwargs): op=OP.ANTHROPIC_MESSAGES_CREATE, description="Anthropic messages create", origin=AnthropicIntegration.origin, + only_if_parent=True, ) span.__enter__() diff --git a/sentry_sdk/integrations/arq.py b/sentry_sdk/integrations/arq.py index 4640204725..345e030fb1 100644 --- a/sentry_sdk/integrations/arq.py +++ b/sentry_sdk/integrations/arq.py @@ -79,7 +79,10 @@ async def _sentry_enqueue_job(self, function, *args, **kwargs): return await old_enqueue_job(self, function, *args, **kwargs) with sentry_sdk.start_span( - op=OP.QUEUE_SUBMIT_ARQ, name=function, origin=ArqIntegration.origin + op=OP.QUEUE_SUBMIT_ARQ, + name=function, + origin=ArqIntegration.origin, + only_if_parent=True, ): return await old_enqueue_job(self, function, *args, **kwargs) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index 7021d7fceb..d9bdf4a592 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -48,6 +48,7 @@ async def _coro_creating_hub_and_span(): op=OP.FUNCTION, name=get_name(coro), origin=AsyncioIntegration.origin, + only_if_parent=True, ): try: result = await coro diff --git a/sentry_sdk/integrations/asyncpg.py b/sentry_sdk/integrations/asyncpg.py index 1113fd199c..7d1654398a 100644 --- a/sentry_sdk/integrations/asyncpg.py +++ b/sentry_sdk/integrations/asyncpg.py @@ -169,6 +169,7 @@ async def _inner(*args: Any, **kwargs: Any) -> T: op=OP.DB, name="connect", origin=AsyncPGIntegration.origin, + only_if_parent=True, ) as span: data = _get_db_data( addr=kwargs.get("addr"), diff --git a/sentry_sdk/integrations/boto3.py b/sentry_sdk/integrations/boto3.py index 195b532e54..dfea7459c3 100644 --- a/sentry_sdk/integrations/boto3.py +++ b/sentry_sdk/integrations/boto3.py @@ -72,6 +72,7 @@ def _sentry_request_created(service_id, request, operation_name, **kwargs): op=OP.HTTP_CLIENT, name=description, origin=Boto3Integration.origin, + only_if_parent=True, ) data = { diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index 104a6755a7..e2ee4de532 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -495,6 +495,7 @@ def sentry_publish(self, *args, **kwargs): op=OP.QUEUE_PUBLISH, name=task_name, origin=CeleryIntegration.origin, + only_if_parent=True, ) as span: if task_id is not None: span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, task_id) diff --git a/sentry_sdk/integrations/clickhouse_driver.py b/sentry_sdk/integrations/clickhouse_driver.py index 245ea0ef71..014db14b68 100644 --- a/sentry_sdk/integrations/clickhouse_driver.py +++ b/sentry_sdk/integrations/clickhouse_driver.py @@ -89,6 +89,7 @@ def _inner(*args: P.args, **kwargs: P.kwargs) -> T: op=OP.DB, name=query, origin=ClickhouseDriverIntegration.origin, + only_if_parent=True, ) connection._sentry_span = span # type: ignore[attr-defined] diff --git a/sentry_sdk/integrations/cohere.py b/sentry_sdk/integrations/cohere.py index b4c2af91da..a80ccb19b3 100644 --- a/sentry_sdk/integrations/cohere.py +++ b/sentry_sdk/integrations/cohere.py @@ -147,6 +147,7 @@ def new_chat(*args, **kwargs): op=consts.OP.COHERE_CHAT_COMPLETIONS_CREATE, name="cohere.client.Chat", origin=CohereIntegration.origin, + only_if_parent=True, ) span.__enter__() try: @@ -233,6 +234,7 @@ def new_embed(*args, **kwargs): op=consts.OP.COHERE_EMBEDDINGS_CREATE, name="Cohere Embedding Creation", origin=CohereIntegration.origin, + only_if_parent=True, ) as span: if "texts" in kwargs and ( should_send_default_pii() and integration.include_prompts diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index ba2cc6a0ad..2fea384ac9 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -687,6 +687,7 @@ def connect(self): op=OP.DB, name="connect", origin=DjangoIntegration.origin_db, + only_if_parent=True, ) as span: _set_db_data(span, self) return real_connect(self) diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 73a25acc9f..daa1498c58 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -184,6 +184,7 @@ async def sentry_wrapped_callback(request, *args, **kwargs): op=OP.VIEW_RENDER, name=request.resolver_match.view_name, origin=DjangoIntegration.origin, + only_if_parent=True, ): return await callback(request, *args, **kwargs) diff --git a/sentry_sdk/integrations/django/caching.py b/sentry_sdk/integrations/django/caching.py index 4bd7cb7236..aa60993155 100644 --- a/sentry_sdk/integrations/django/caching.py +++ b/sentry_sdk/integrations/django/caching.py @@ -54,6 +54,7 @@ def _instrument_call( op=op, name=description, origin=DjangoIntegration.origin, + only_if_parent=True, ) as span: value = original_method(*args, **kwargs) diff --git a/sentry_sdk/integrations/django/middleware.py b/sentry_sdk/integrations/django/middleware.py index 245276566e..6640ac2919 100644 --- a/sentry_sdk/integrations/django/middleware.py +++ b/sentry_sdk/integrations/django/middleware.py @@ -89,6 +89,7 @@ def _check_middleware_span(old_method): op=OP.MIDDLEWARE_DJANGO, name=description, origin=DjangoIntegration.origin, + only_if_parent=True, ) middleware_span.set_tag("django.function_name", function_name) middleware_span.set_tag("django.middleware_name", middleware_name) diff --git a/sentry_sdk/integrations/django/signals_handlers.py b/sentry_sdk/integrations/django/signals_handlers.py index cb0f8b9d2e..ae948cec2a 100644 --- a/sentry_sdk/integrations/django/signals_handlers.py +++ b/sentry_sdk/integrations/django/signals_handlers.py @@ -68,6 +68,7 @@ def wrapper(*args, **kwargs): op=OP.EVENT_DJANGO, name=signal_name, origin=DjangoIntegration.origin, + only_if_parent=True, ) as span: span.set_data("signal", signal_name) return receiver(*args, **kwargs) diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index f5309c9cf3..c9e41e24a0 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -72,6 +72,7 @@ def rendered_content(self): op=OP.TEMPLATE_RENDER, name=_get_template_name_description(self.template_name), origin=DjangoIntegration.origin, + only_if_parent=True, ) as span: if isinstance(self.context_data, dict): for k, v in self.context_data.items(): @@ -102,6 +103,7 @@ def render(request, template_name, context=None, *args, **kwargs): op=OP.TEMPLATE_RENDER, name=_get_template_name_description(template_name), origin=DjangoIntegration.origin, + only_if_parent=True, ) as span: for k, v in context.items(): span.set_data(f"context.{k}", v) diff --git a/sentry_sdk/integrations/django/views.py b/sentry_sdk/integrations/django/views.py index 0a9861a6a6..e8dfa8abb6 100644 --- a/sentry_sdk/integrations/django/views.py +++ b/sentry_sdk/integrations/django/views.py @@ -37,6 +37,7 @@ def sentry_patched_render(self): op=OP.VIEW_RESPONSE_RENDER, name="serialize response", origin=DjangoIntegration.origin, + only_if_parent=True, ): return old_render(self) @@ -90,6 +91,7 @@ def sentry_wrapped_callback(request, *args, **kwargs): op=OP.VIEW_RENDER, name=request.resolver_match.view_name, origin=DjangoIntegration.origin, + only_if_parent=True, ): return callback(request, *args, **kwargs) diff --git a/sentry_sdk/integrations/graphene.py b/sentry_sdk/integrations/graphene.py index 03731dcaaa..828bb0ade5 100644 --- a/sentry_sdk/integrations/graphene.py +++ b/sentry_sdk/integrations/graphene.py @@ -144,7 +144,9 @@ def graphql_span(schema, source, kwargs): if scope.span: _graphql_span = scope.span.start_child(op=op, name=operation_name) else: - _graphql_span = sentry_sdk.start_span(op=op, name=operation_name) + _graphql_span = sentry_sdk.start_span( + op=op, name=operation_name, only_if_parent=True + ) _graphql_span.set_data("graphql.document", source) _graphql_span.set_data("graphql.operation.name", operation_name) diff --git a/sentry_sdk/integrations/grpc/aio/client.py b/sentry_sdk/integrations/grpc/aio/client.py index e8adeba05e..d1f4b352d3 100644 --- a/sentry_sdk/integrations/grpc/aio/client.py +++ b/sentry_sdk/integrations/grpc/aio/client.py @@ -52,6 +52,7 @@ async def intercept_unary_unary( op=OP.GRPC_CLIENT, name="unary unary call to %s" % method.decode(), origin=SPAN_ORIGIN, + only_if_parent=True, ) as span: span.set_data("type", "unary unary") span.set_data("method", method) @@ -82,6 +83,7 @@ async def intercept_unary_stream( op=OP.GRPC_CLIENT, name="unary stream call to %s" % method.decode(), origin=SPAN_ORIGIN, + only_if_parent=True, ) as span: span.set_data("type", "unary stream") span.set_data("method", method) diff --git a/sentry_sdk/integrations/grpc/client.py b/sentry_sdk/integrations/grpc/client.py index a5b4f9f52e..cb456fc9b4 100644 --- a/sentry_sdk/integrations/grpc/client.py +++ b/sentry_sdk/integrations/grpc/client.py @@ -31,6 +31,7 @@ def intercept_unary_unary(self, continuation, client_call_details, request): op=OP.GRPC_CLIENT, name="unary unary call to %s" % method, origin=SPAN_ORIGIN, + only_if_parent=True, ) as span: span.set_data("type", "unary unary") span.set_data("method", method) @@ -52,6 +53,7 @@ def intercept_unary_stream(self, continuation, client_call_details, request): op=OP.GRPC_CLIENT, name="unary stream call to %s" % method, origin=SPAN_ORIGIN, + only_if_parent=True, ) as span: span.set_data("type", "unary stream") span.set_data("method", method) diff --git a/sentry_sdk/integrations/httpx.py b/sentry_sdk/integrations/httpx.py index c4d8e4b4dc..338106c01b 100644 --- a/sentry_sdk/integrations/httpx.py +++ b/sentry_sdk/integrations/httpx.py @@ -59,6 +59,7 @@ def send(self, request, **kwargs): parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, ), origin=HttpxIntegration.origin, + only_if_parent=True, ) as span: data = { SPANDATA.HTTP_METHOD: request.method, @@ -129,6 +130,7 @@ async def send(self, request, **kwargs): parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, ), origin=HttpxIntegration.origin, + only_if_parent=True, ) as span: data = { SPANDATA.HTTP_METHOD: request.method, diff --git a/sentry_sdk/integrations/huey.py b/sentry_sdk/integrations/huey.py index dae3cf6dfa..9a3bfb9b99 100644 --- a/sentry_sdk/integrations/huey.py +++ b/sentry_sdk/integrations/huey.py @@ -61,6 +61,7 @@ def _sentry_enqueue(self, task): op=OP.QUEUE_SUBMIT_HUEY, name=task.name, origin=HueyIntegration.origin, + only_if_parent=True, ): if not isinstance(task, PeriodicTask): # Attach trace propagation data to task kwargs. We do diff --git a/sentry_sdk/integrations/huggingface_hub.py b/sentry_sdk/integrations/huggingface_hub.py index d09f6e2163..ae00618995 100644 --- a/sentry_sdk/integrations/huggingface_hub.py +++ b/sentry_sdk/integrations/huggingface_hub.py @@ -77,6 +77,7 @@ def new_text_generation(*args, **kwargs): op=consts.OP.HUGGINGFACE_HUB_CHAT_COMPLETIONS_CREATE, name="Text Generation", origin=HuggingfaceHubIntegration.origin, + only_if_parent=True, ) span.__enter__() try: diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 431fc46bec..afce913d8e 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -143,7 +143,9 @@ def _create_span(self, run_id, parent_id, **kwargs): watched_span = WatchedSpan(parent_span.span.start_child(**kwargs)) parent_span.children.append(watched_span) if watched_span is None: - watched_span = WatchedSpan(sentry_sdk.start_span(**kwargs)) + watched_span = WatchedSpan( + sentry_sdk.start_span(only_if_parent=True, **kwargs) + ) if kwargs.get("op", "").startswith("ai.pipeline."): if kwargs.get("name"): diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 4b04dada8a..25e49696db 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -141,6 +141,7 @@ async def _create_span_call(self, scope, receive, send): op=OP.MIDDLEWARE_LITESTAR, name=middleware_name, origin=LitestarIntegration.origin, + only_if_parent=True, ) as middleware_span: middleware_span.set_tag("litestar.middleware_name", middleware_name) @@ -153,6 +154,7 @@ async def _sentry_receive(*args, **kwargs): op=OP.MIDDLEWARE_LITESTAR_RECEIVE, name=getattr(receive, "__qualname__", str(receive)), origin=LitestarIntegration.origin, + only_if_parent=True, ) as span: span.set_tag("litestar.middleware_name", middleware_name) return await receive(*args, **kwargs) @@ -170,6 +172,7 @@ async def _sentry_send(message): op=OP.MIDDLEWARE_LITESTAR_SEND, name=getattr(send, "__qualname__", str(send)), origin=LitestarIntegration.origin, + only_if_parent=True, ) as span: span.set_tag("litestar.middleware_name", middleware_name) return await send(message) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index e6ac36f3cb..86d308fdea 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -139,6 +139,7 @@ def _new_chat_completion_common(f, *args, **kwargs): op=consts.OP.OPENAI_CHAT_COMPLETIONS_CREATE, description="Chat Completion", origin=OpenAIIntegration.origin, + only_if_parent=True, ) span.__enter__() @@ -324,6 +325,7 @@ def _new_embeddings_create_common(f, *args, **kwargs): op=consts.OP.OPENAI_EMBEDDINGS_CREATE, description="OpenAI Embedding Creation", origin=OpenAIIntegration.origin, + only_if_parent=True, ) as span: if "input" in kwargs and ( should_send_default_pii() and integration.include_prompts diff --git a/sentry_sdk/integrations/pymongo.py b/sentry_sdk/integrations/pymongo.py index f03f70606b..32cb294075 100644 --- a/sentry_sdk/integrations/pymongo.py +++ b/sentry_sdk/integrations/pymongo.py @@ -153,6 +153,7 @@ def started(self, event): op=OP.DB, name=query, origin=PyMongoIntegration.origin, + only_if_parent=True, ) with capture_internal_exceptions(): diff --git a/sentry_sdk/integrations/ray.py b/sentry_sdk/integrations/ray.py index d1910026ab..0290bdf1ef 100644 --- a/sentry_sdk/integrations/ray.py +++ b/sentry_sdk/integrations/ray.py @@ -88,6 +88,7 @@ def _remote_method_with_header_propagation(*args, **kwargs): op=OP.QUEUE_SUBMIT_RAY, name=qualname_from_function(f), origin=RayIntegration.origin, + only_if_parent=True, ) as span: tracing = { k: v diff --git a/sentry_sdk/integrations/redis/_async_common.py b/sentry_sdk/integrations/redis/_async_common.py index f835affbf3..e62aa1a807 100644 --- a/sentry_sdk/integrations/redis/_async_common.py +++ b/sentry_sdk/integrations/redis/_async_common.py @@ -40,6 +40,7 @@ async def _sentry_execute(self, *args, **kwargs): op=OP.DB_REDIS, name="redis.pipeline.execute", origin=SPAN_ORIGIN, + only_if_parent=True, ) as span: with capture_internal_exceptions(): span_data = get_db_data_fn(self) @@ -84,6 +85,7 @@ async def _sentry_execute_command(self, name, *args, **kwargs): op=cache_properties["op"], name=cache_properties["description"], origin=SPAN_ORIGIN, + only_if_parent=True, ) cache_span.__enter__() @@ -93,6 +95,7 @@ async def _sentry_execute_command(self, name, *args, **kwargs): op=db_properties["op"], name=db_properties["description"], origin=SPAN_ORIGIN, + only_if_parent=True, ) db_span.__enter__() diff --git a/sentry_sdk/integrations/redis/_sync_common.py b/sentry_sdk/integrations/redis/_sync_common.py index 9c96ad61d1..63738ea7cb 100644 --- a/sentry_sdk/integrations/redis/_sync_common.py +++ b/sentry_sdk/integrations/redis/_sync_common.py @@ -41,6 +41,7 @@ def sentry_patched_execute(self, *args, **kwargs): op=OP.DB_REDIS, name="redis.pipeline.execute", origin=SPAN_ORIGIN, + only_if_parent=True, ) as span: with capture_internal_exceptions(): span_data = get_db_data_fn(self) diff --git a/sentry_sdk/integrations/socket.py b/sentry_sdk/integrations/socket.py index 0866ceb608..cba448c3a3 100644 --- a/sentry_sdk/integrations/socket.py +++ b/sentry_sdk/integrations/socket.py @@ -57,6 +57,7 @@ def create_connection( op=OP.SOCKET_CONNECTION, name=_get_span_description(address[0], address[1]), origin=SocketIntegration.origin, + only_if_parent=True, ) as span: span.set_data("address", address) span.set_data("timeout", timeout) @@ -83,6 +84,7 @@ def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): op=OP.SOCKET_DNS, name=_get_span_description(host, port), origin=SocketIntegration.origin, + only_if_parent=True, ) as span: span.set_data("host", host) span.set_data("port", port) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index d9db8bd6b8..c8a415a64d 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -164,6 +164,7 @@ async def _create_span_call(app, scope, receive, send, **kwargs): op=OP.MIDDLEWARE_STARLETTE, name=middleware_name, origin=StarletteIntegration.origin, + only_if_parent=True, ) as middleware_span: middleware_span.set_tag("starlette.middleware_name", middleware_name) @@ -174,6 +175,7 @@ async def _sentry_receive(*args, **kwargs): op=OP.MIDDLEWARE_STARLETTE_RECEIVE, name=getattr(receive, "__qualname__", str(receive)), origin=StarletteIntegration.origin, + only_if_parent=True, ) as span: span.set_tag("starlette.middleware_name", middleware_name) return await receive(*args, **kwargs) @@ -189,6 +191,7 @@ async def _sentry_send(*args, **kwargs): op=OP.MIDDLEWARE_STARLETTE_SEND, name=getattr(send, "__qualname__", str(send)), origin=StarletteIntegration.origin, + only_if_parent=True, ) as span: span.set_tag("starlette.middleware_name", middleware_name) return await send(*args, **kwargs) diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index 8714ee2f08..66f5025c26 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -140,6 +140,7 @@ async def _create_span_call(self, scope, receive, send): op=OP.MIDDLEWARE_STARLITE, name=middleware_name, origin=StarliteIntegration.origin, + only_if_parent=True, ) as middleware_span: middleware_span.set_tag("starlite.middleware_name", middleware_name) @@ -152,6 +153,7 @@ async def _sentry_receive(*args, **kwargs): op=OP.MIDDLEWARE_STARLITE_RECEIVE, name=getattr(receive, "__qualname__", str(receive)), origin=StarliteIntegration.origin, + only_if_parent=True, ) as span: span.set_tag("starlite.middleware_name", middleware_name) return await receive(*args, **kwargs) @@ -169,6 +171,7 @@ async def _sentry_send(message): op=OP.MIDDLEWARE_STARLITE_SEND, name=getattr(send, "__qualname__", str(send)), origin=StarliteIntegration.origin, + only_if_parent=True, ) as span: span.set_tag("starlite.middleware_name", middleware_name) return await send(message) diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index 9ddfe7468d..424e7b88aa 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -94,6 +94,7 @@ def putrequest(self, method, url, *args, **kwargs): name="%s %s" % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), origin="auto.http.stdlib.httplib", + only_if_parent=True, ) data = { @@ -225,6 +226,7 @@ def sentry_patched_popen_init(self, *a, **kw): op=OP.SUBPROCESS, name=description, origin="auto.subprocess.stdlib.subprocess", + only_if_parent=True, ) as span: for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers( span=span @@ -275,6 +277,7 @@ def sentry_patched_popen_wait(self, *a, **kw): with sentry_sdk.start_span( op=OP.SUBPROCESS_WAIT, origin="auto.subprocess.stdlib.subprocess", + only_if_parent=True, ) as span: span.set_tag("subprocess.pid", self.pid) return old_popen_wait(self, *a, **kw) @@ -289,6 +292,7 @@ def sentry_patched_popen_communicate(self, *a, **kw): with sentry_sdk.start_span( op=OP.SUBPROCESS_COMMUNICATE, origin="auto.subprocess.stdlib.subprocess", + only_if_parent=True, ) as span: span.set_tag("subprocess.pid", self.pid) return old_popen_communicate(self, *a, **kw) diff --git a/sentry_sdk/integrations/strawberry.py b/sentry_sdk/integrations/strawberry.py index 636779b61d..bf174e9d99 100644 --- a/sentry_sdk/integrations/strawberry.py +++ b/sentry_sdk/integrations/strawberry.py @@ -191,6 +191,7 @@ def on_operation(self): op=op, name=description, origin=StrawberryIntegration.origin, + only_if_parent=True, ) self.graphql_span.set_data("graphql.operation.type", operation_type) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index c2bd5734c5..aba7d4f49d 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -10,6 +10,8 @@ format_span_id, Span as OtelSpan, TraceState, + get_current_span, + INVALID_SPAN, ) from opentelemetry.trace.status import StatusCode from opentelemetry.sdk.trace import ReadableSpan @@ -683,9 +685,9 @@ def get_trace_context(self): rv["status"] = self.status if self.containing_transaction: - rv[ - "dynamic_sampling_context" - ] = self.containing_transaction.get_baggage().dynamic_sampling_context() + rv["dynamic_sampling_context"] = ( + self.containing_transaction.get_baggage().dynamic_sampling_context() + ) data = {} @@ -1204,6 +1206,7 @@ def __init__( origin=None, # type: Optional[str] name=None, # type: Optional[str] source=TRANSACTION_SOURCE_CUSTOM, # type: str + only_if_parent=False, # type: bool otel_span=None, # type: Optional[OtelSpan] **_, # type: dict[str, object] ): @@ -1214,29 +1217,40 @@ def __init__( listed in the signature. These additional arguments are ignored. If otel_span is passed explicitly, just acts as a proxy. + + If only_if_parent is True, just return an INVALID_SPAN + and avoid instrumentation if there's no active parent span. """ if otel_span is not None: self._otel_span = otel_span else: - from sentry_sdk.integrations.opentelemetry.utils import ( - convert_to_otel_timestamp, + skip_span = ( + only_if_parent and not get_current_span().get_span_context().is_valid ) + if skip_span: + self._otel_span = INVALID_SPAN + else: + from sentry_sdk.integrations.opentelemetry.utils import ( + convert_to_otel_timestamp, + ) - if start_timestamp is not None: - # OTel timestamps have nanosecond precision - start_timestamp = convert_to_otel_timestamp(start_timestamp) + if start_timestamp is not None: + # OTel timestamps have nanosecond precision + start_timestamp = convert_to_otel_timestamp(start_timestamp) - span_name = name or description or op or "" - self._otel_span = tracer.start_span(span_name, start_time=start_timestamp) + span_name = name or description or op or "" + self._otel_span = tracer.start_span( + span_name, start_time=start_timestamp + ) - self.origin = origin or DEFAULT_SPAN_ORIGIN - self.op = op - self.description = description - self.name = span_name - self.source = source + self.origin = origin or DEFAULT_SPAN_ORIGIN + self.op = op + self.description = description + self.name = span_name + self.source = source - if status is not None: - self.set_status(status) + if status is not None: + self.set_status(status) def __eq__(self, other): # type: (POTelSpan) -> bool @@ -1454,7 +1468,7 @@ def start_child(self, **kwargs): # type: (**Any) -> POTelSpan kwargs.setdefault("sampled", self.sampled) - span = POTelSpan(**kwargs) + span = POTelSpan(only_if_parent=True, **kwargs) return span def iter_headers(self):