From 2c3f6c78d7c6c6b3717d79a0c1478023a87d8b18 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:21:17 +0000 Subject: [PATCH 1/2] fix(otlp)!: Don't set up a propagator automatically Per the OTLP integration spec, the OTLPIntegration must not set up an automatic propagator. Remove the setup_propagator option and stop replacing the global textmap with SentryOTLPPropagator. The propagator stays available for manual setup. Closes #6732 Co-Authored-By: Ivana Kellyer --- MIGRATION_GUIDE.md | 13 +++++++++++++ sentry_sdk/integrations/otlp.py | 18 ++++++++---------- tests/integrations/otlp/test_otlp.py | 21 ++++----------------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index e71c2450c0..d9b89eabc5 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -52,6 +52,19 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - Whether the MCP integration records tool arguments, tool results, and prompt contents is now controlled by `data_collection`'s new `mcp` setting (`{"inputs": bool, "outputs": bool}`, both `True` by default) instead of the `gen_ai` setting. - With `handled_tool_call_exceptions=True` (the default), the Pydantic AI integration now reports an error for every failed tool argument validation attempt, including retries. Previously, only one error was reported per tool call. - The Pydantic AI integration no longer sets the `gen_ai.response.streaming` span attribute. +- The `OTLPIntegration` no longer sets up a propagator, and its `setup_propagator` option was removed. Distributed tracing now uses the propagators configured in OpenTelemetry (by default, W3C `traceparent` and `baggage`). If you need to propagate Sentry's `sentry-trace` and `baggage` headers, for example to talk to services instrumented with Sentry SDKs instead of OpenTelemetry, set up the `SentryOTLPPropagator` manually: + + ```python + from opentelemetry.propagate import set_global_textmap + from opentelemetry.propagators.composite import CompositePropagator + from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + + from sentry_sdk.integrations.otlp import SentryOTLPPropagator + + set_global_textmap( + CompositePropagator([TraceContextTextMapPropagator(), SentryOTLPPropagator()]) + ) + ``` #### Span operation consolidation diff --git a/sentry_sdk/integrations/otlp.py b/sentry_sdk/integrations/otlp.py index bbf3ffb9cf..2b47dcb9cd 100644 --- a/sentry_sdk/integrations/otlp.py +++ b/sentry_sdk/integrations/otlp.py @@ -24,7 +24,6 @@ set_value, ) from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter - from opentelemetry.propagate import set_global_textmap from opentelemetry.propagators.textmap import ( CarrierT, Getter, @@ -132,6 +131,14 @@ def _sentry_patched_record_exception( class SentryOTLPPropagator(TextMapPropagator): """ + Propagator for the Sentry-specific ``sentry-trace`` and ``baggage`` headers. + + The OTLPIntegration doesn't set up any propagator. OpenTelemetry's default + propagators (W3C ``traceparent`` and ``baggage``) are used unless configured + otherwise. If you need to continue traces from or propagate traces to services + that only understand Sentry's headers, set this propagator up manually with + ``opentelemetry.propagate.set_global_textmap``. + !!! Note regarding baggage: We cannot meaningfully populate a new baggage as a head SDK when we are using OTLP since we don't have any sort of transaction semantic to @@ -238,8 +245,6 @@ class OTLPIntegration(Integration): Set to False to setup the TracerProvider manually. :param collector_url: URL of your own OpenTelemetry collector, defaults to None. When set, the exporter will send traces to this URL instead of the Sentry OTLP endpoint derived from the DSN. - :param setup_propagator: Automatically configure the Sentry Propagator for Distributed Tracing, defaults to True. - Set to False to configure propagators manually or to disable propagation. :param capture_exceptions: Intercept and capture exceptions on the OpenTelemetry Span in Sentry as well, defaults to False. Set to True to turn on capturing but be aware that since Sentry captures most exceptions, duplicate exceptions might be dropped by DedupeIntegration in many cases. """ @@ -250,12 +255,10 @@ def __init__( self, setup_otlp_traces_exporter: bool = True, collector_url: "Optional[str]" = None, - setup_propagator: bool = True, capture_exceptions: bool = False, ) -> None: self.setup_otlp_traces_exporter = setup_otlp_traces_exporter self.collector_url = collector_url - self.setup_propagator = setup_propagator self.capture_exceptions = capture_exceptions @staticmethod @@ -271,9 +274,4 @@ def setup_once_with_options( dsn: "Optional[str]" = options.get("dsn") if options else None setup_otlp_traces_exporter(dsn, collector_url=self.collector_url) - if self.setup_propagator: - logger.debug("[OTLP] Setting up propagator for distributed tracing") - # TODO-neel better propagator support, chain with existing ones if possible instead of replacing - set_global_textmap(SentryOTLPPropagator()) - setup_capture_exceptions() diff --git a/tests/integrations/otlp/test_otlp.py b/tests/integrations/otlp/test_otlp.py index 0537b3192e..c372f35636 100644 --- a/tests/integrations/otlp/test_otlp.py +++ b/tests/integrations/otlp/test_otlp.py @@ -151,26 +151,13 @@ def test_does_not_setup_exporter_when_disabled(sentry_init): assert tracer_provider is existing_tracer_provider -def test_sets_propagator(sentry_init): +def test_does_not_set_propagator(sentry_init): sentry_init( dsn="https://mysecret@bla.ingest.sentry.io/12312012", integrations=[OTLPIntegration()], ) - propagator = get_global_textmap() - assert isinstance(get_global_textmap(), SentryOTLPPropagator) - assert propagator is not original_propagator - - -def test_does_not_set_propagator_if_disabled(sentry_init): - sentry_init( - dsn="https://mysecret@bla.ingest.sentry.io/12312012", - integrations=[OTLPIntegration(setup_propagator=False)], - ) - - propagator = get_global_textmap() - assert not isinstance(propagator, SentryOTLPPropagator) - assert propagator is original_propagator + assert get_global_textmap() is original_propagator def test_otel_propagation_context(sentry_init): @@ -198,7 +185,7 @@ def test_propagator_inject_head_of_trace(sentry_init): ) tracer = trace.get_tracer(__name__) - propagator = get_global_textmap() + propagator = SentryOTLPPropagator() carrier = {} with tracer.start_as_current_span("foo") as span: @@ -222,7 +209,7 @@ def test_propagator_inject_continue_trace(sentry_init): ) tracer = trace.get_tracer(__name__) - propagator = get_global_textmap() + propagator = SentryOTLPPropagator() carrier = {} incoming_headers = { From 48a5db2cd6bf31400ccfc30f6020fa855065f3af Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 6 Nov 2026 10:02:45 +0000 Subject: [PATCH 2/2] fix(otlp)!: Keep setup_propagator, default it to False Follow the sentry-ruby approach (getsentry/sentry-ruby#3063) and keep setup_propagator as an opt-in instead of removing it. Co-Authored-By: Ivana Kellyer --- MIGRATION_GUIDE.md | 2 +- sentry_sdk/integrations/otlp.py | 18 ++++++++++-------- tests/integrations/otlp/test_otlp.py | 19 ++++++++++++++----- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index d9b89eabc5..b9e08520a6 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -52,7 +52,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - Whether the MCP integration records tool arguments, tool results, and prompt contents is now controlled by `data_collection`'s new `mcp` setting (`{"inputs": bool, "outputs": bool}`, both `True` by default) instead of the `gen_ai` setting. - With `handled_tool_call_exceptions=True` (the default), the Pydantic AI integration now reports an error for every failed tool argument validation attempt, including retries. Previously, only one error was reported per tool call. - The Pydantic AI integration no longer sets the `gen_ai.response.streaming` span attribute. -- The `OTLPIntegration` no longer sets up a propagator, and its `setup_propagator` option was removed. Distributed tracing now uses the propagators configured in OpenTelemetry (by default, W3C `traceparent` and `baggage`). If you need to propagate Sentry's `sentry-trace` and `baggage` headers, for example to talk to services instrumented with Sentry SDKs instead of OpenTelemetry, set up the `SentryOTLPPropagator` manually: +- The `OTLPIntegration` no longer sets up a propagator by default: `setup_propagator` now defaults to `False`. Distributed tracing now uses the propagators configured in OpenTelemetry (by default, W3C `traceparent` and `baggage`) instead of Sentry's `sentry-trace` and `baggage` headers. To get the old behavior back, set `OTLPIntegration(setup_propagator=True)`. To propagate both W3C and Sentry headers, set up the `SentryOTLPPropagator` manually instead: ```python from opentelemetry.propagate import set_global_textmap diff --git a/sentry_sdk/integrations/otlp.py b/sentry_sdk/integrations/otlp.py index 2b47dcb9cd..da6e4e50a9 100644 --- a/sentry_sdk/integrations/otlp.py +++ b/sentry_sdk/integrations/otlp.py @@ -24,6 +24,7 @@ set_value, ) from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter + from opentelemetry.propagate import set_global_textmap from opentelemetry.propagators.textmap import ( CarrierT, Getter, @@ -131,14 +132,6 @@ def _sentry_patched_record_exception( class SentryOTLPPropagator(TextMapPropagator): """ - Propagator for the Sentry-specific ``sentry-trace`` and ``baggage`` headers. - - The OTLPIntegration doesn't set up any propagator. OpenTelemetry's default - propagators (W3C ``traceparent`` and ``baggage``) are used unless configured - otherwise. If you need to continue traces from or propagate traces to services - that only understand Sentry's headers, set this propagator up manually with - ``opentelemetry.propagate.set_global_textmap``. - !!! Note regarding baggage: We cannot meaningfully populate a new baggage as a head SDK when we are using OTLP since we don't have any sort of transaction semantic to @@ -245,6 +238,8 @@ class OTLPIntegration(Integration): Set to False to setup the TracerProvider manually. :param collector_url: URL of your own OpenTelemetry collector, defaults to None. When set, the exporter will send traces to this URL instead of the Sentry OTLP endpoint derived from the DSN. + :param setup_propagator: Replace the global OpenTelemetry propagator with the Sentry Propagator, defaults to False. + By default, the propagators configured in OpenTelemetry are used for Distributed Tracing. :param capture_exceptions: Intercept and capture exceptions on the OpenTelemetry Span in Sentry as well, defaults to False. Set to True to turn on capturing but be aware that since Sentry captures most exceptions, duplicate exceptions might be dropped by DedupeIntegration in many cases. """ @@ -255,10 +250,12 @@ def __init__( self, setup_otlp_traces_exporter: bool = True, collector_url: "Optional[str]" = None, + setup_propagator: bool = False, capture_exceptions: bool = False, ) -> None: self.setup_otlp_traces_exporter = setup_otlp_traces_exporter self.collector_url = collector_url + self.setup_propagator = setup_propagator self.capture_exceptions = capture_exceptions @staticmethod @@ -274,4 +271,9 @@ def setup_once_with_options( dsn: "Optional[str]" = options.get("dsn") if options else None setup_otlp_traces_exporter(dsn, collector_url=self.collector_url) + if self.setup_propagator: + logger.debug("[OTLP] Setting up propagator for distributed tracing") + # TODO-neel better propagator support, chain with existing ones if possible instead of replacing + set_global_textmap(SentryOTLPPropagator()) + setup_capture_exceptions() diff --git a/tests/integrations/otlp/test_otlp.py b/tests/integrations/otlp/test_otlp.py index c372f35636..1bb5228d4f 100644 --- a/tests/integrations/otlp/test_otlp.py +++ b/tests/integrations/otlp/test_otlp.py @@ -151,7 +151,7 @@ def test_does_not_setup_exporter_when_disabled(sentry_init): assert tracer_provider is existing_tracer_provider -def test_does_not_set_propagator(sentry_init): +def test_does_not_set_propagator_by_default(sentry_init): sentry_init( dsn="https://mysecret@bla.ingest.sentry.io/12312012", integrations=[OTLPIntegration()], @@ -160,6 +160,15 @@ def test_does_not_set_propagator(sentry_init): assert get_global_textmap() is original_propagator +def test_sets_propagator_if_enabled(sentry_init): + sentry_init( + dsn="https://mysecret@bla.ingest.sentry.io/12312012", + integrations=[OTLPIntegration(setup_propagator=True)], + ) + + assert isinstance(get_global_textmap(), SentryOTLPPropagator) + + def test_otel_propagation_context(sentry_init): sentry_init( dsn="https://mysecret@bla.ingest.sentry.io/12312012", @@ -181,11 +190,11 @@ def test_otel_propagation_context(sentry_init): def test_propagator_inject_head_of_trace(sentry_init): sentry_init( dsn="https://mysecret@bla.ingest.sentry.io/12312012", - integrations=[OTLPIntegration()], + integrations=[OTLPIntegration(setup_propagator=True)], ) tracer = trace.get_tracer(__name__) - propagator = SentryOTLPPropagator() + propagator = get_global_textmap() carrier = {} with tracer.start_as_current_span("foo") as span: @@ -205,11 +214,11 @@ def test_propagator_inject_head_of_trace(sentry_init): def test_propagator_inject_continue_trace(sentry_init): sentry_init( dsn="https://mysecret@bla.ingest.sentry.io/12312012", - integrations=[OTLPIntegration()], + integrations=[OTLPIntegration(setup_propagator=True)], ) tracer = trace.get_tracer(__name__) - propagator = SentryOTLPPropagator() + propagator = get_global_textmap() carrier = {} incoming_headers = {