Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions sentry_sdk/integrations/otlp.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ def otel_propagation_context() -> "Optional[Tuple[str, str]]":
return (format_trace_id(ctx.trace_id), format_span_id(ctx.span_id))


def setup_otlp_traces_exporter(dsn: "Optional[str]" = None) -> None:
def setup_otlp_traces_exporter(
dsn: "Optional[str]" = None, collector_url: "Optional[str]" = None
) -> None:
tracer_provider = get_tracer_provider()

if not isinstance(tracer_provider, TracerProvider):
Expand All @@ -76,7 +78,10 @@ def setup_otlp_traces_exporter(dsn: "Optional[str]" = None) -> None:

endpoint = None
headers = None
if dsn:
if collector_url:
endpoint = collector_url
logger.debug(f"[OTLP] Sending traces to collector at {endpoint}")
elif dsn:
auth = Dsn(dsn).to_auth(f"sentry.python/{VERSION}")
endpoint = auth.get_api_url(EndpointType.OTLP_TRACES)
headers = {"X-Sentry-Auth": auth.to_header()}
Expand Down Expand Up @@ -177,7 +182,9 @@ class OTLPIntegration(Integration):
Automatically setup OTLP ingestion from the DSN.

:param setup_otlp_traces_exporter: Automatically configure an Exporter to send OTLP traces from the DSN, defaults to True.
Set to False if using a custom collector or to setup the TracerProvider manually.
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.
Expand All @@ -189,10 +196,12 @@ class OTLPIntegration(Integration):
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

Expand All @@ -207,7 +216,7 @@ def setup_once_with_options(
if self.setup_otlp_traces_exporter:
logger.debug("[OTLP] Setting up OTLP exporter")
dsn: "Optional[str]" = options.get("dsn") if options else None
setup_otlp_traces_exporter(dsn)
setup_otlp_traces_exporter(dsn, collector_url=self.collector_url)

if self.setup_propagator:
logger.debug("[OTLP] Setting up propagator for distributed tracing")
Expand Down
66 changes: 66 additions & 0 deletions tests/integrations/otlp/test_otlp.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def mock_otlp_ingest():
url="https://bla.ingest.sentry.io/api/12312012/integration/otlp/v1/traces/",
status=200,
)
responses.add(
responses.POST,
url="https://my-collector.example.com/v1/traces",
status=200,
)

yield

Expand Down Expand Up @@ -233,6 +238,67 @@ def test_propagator_inject_continue_trace(sentry_init):
detach(token)


def test_collector_url_sets_endpoint(sentry_init):
sentry_init(
dsn="https://mysecret@bla.ingest.sentry.io/12312012",
integrations=[
OTLPIntegration(collector_url="https://my-collector.example.com/v1/traces")
],
)

tracer_provider = get_tracer_provider()
assert isinstance(tracer_provider, TracerProvider)

(span_processor,) = tracer_provider._active_span_processor._span_processors
assert isinstance(span_processor, BatchSpanProcessor)

exporter = span_processor.span_exporter
assert isinstance(exporter, OTLPSpanExporter)
assert exporter._endpoint == "https://my-collector.example.com/v1/traces"
assert exporter._headers is None or "X-Sentry-Auth" not in exporter._headers


def test_collector_url_takes_precedence_over_dsn(sentry_init):
sentry_init(
dsn="https://mysecret@bla.ingest.sentry.io/12312012",
integrations=[
OTLPIntegration(collector_url="https://my-collector.example.com/v1/traces")
],
)

tracer_provider = get_tracer_provider()
assert isinstance(tracer_provider, TracerProvider)

(span_processor,) = tracer_provider._active_span_processor._span_processors
exporter = span_processor.span_exporter
assert isinstance(exporter, OTLPSpanExporter)
# Should use collector_url, NOT the DSN-derived endpoint
assert exporter._endpoint == "https://my-collector.example.com/v1/traces"
assert (
exporter._endpoint
!= "https://bla.ingest.sentry.io/api/12312012/integration/otlp/v1/traces/"
)


def test_collector_url_none_falls_back_to_dsn(sentry_init):
sentry_init(
dsn="https://mysecret@bla.ingest.sentry.io/12312012",
integrations=[OTLPIntegration(collector_url=None)],
)

tracer_provider = get_tracer_provider()
assert isinstance(tracer_provider, TracerProvider)

(span_processor,) = tracer_provider._active_span_processor._span_processors
exporter = span_processor.span_exporter
assert isinstance(exporter, OTLPSpanExporter)
assert (
exporter._endpoint
== "https://bla.ingest.sentry.io/api/12312012/integration/otlp/v1/traces/"
)
assert "X-Sentry-Auth" in exporter._headers


def test_capture_exceptions_enabled(sentry_init, capture_events):
sentry_init(
dsn="https://mysecret@bla.ingest.sentry.io/12312012",
Expand Down
Loading