Skip to content

Commit 48203ba

Browse files
committed
feat(otlp): Optionally capture exceptions from otel's Span.record_exception api
1 parent eedd101 commit 48203ba

File tree

1 file changed

+45
-4
lines changed

1 file changed

+45
-4
lines changed

sentry_sdk/integrations/otlp.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from sentry_sdk import get_client
1+
from sentry_sdk import get_client, capture_event
22
from sentry_sdk.integrations import Integration, DidNotEnable
33
from sentry_sdk.scope import register_external_propagation_context
4-
from sentry_sdk.utils import logger, Dsn
4+
from sentry_sdk.utils import Dsn, logger, event_from_exception
55
from sentry_sdk.consts import VERSION, EndpointType
66
from sentry_sdk.tracing_utils import Baggage
77
from sentry_sdk.tracing import (
@@ -11,7 +11,7 @@
1111

1212
try:
1313
from opentelemetry.propagate import set_global_textmap
14-
from opentelemetry.sdk.trace import TracerProvider
14+
from opentelemetry.sdk.trace import TracerProvider, Span
1515
from opentelemetry.sdk.trace.export import BatchSpanProcessor
1616
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
1717

@@ -82,6 +82,28 @@ def setup_otlp_traces_exporter(dsn: "Optional[str]" = None) -> None:
8282
tracer_provider.add_span_processor(span_processor)
8383

8484

85+
def setup_capture_exceptions() -> None:
86+
"""
87+
Intercept otel's Span.record_exception to automatically capture those exceptions in Sentry.
88+
"""
89+
_original_record_exception = Span.record_exception
90+
91+
def _sentry_patched_record_exception(
92+
self: "Span", exception: "BaseException", *args: "Any", **kwargs: "Any"
93+
) -> None:
94+
_original_record_exception(self, exception, *args, **kwargs)
95+
96+
event, hint = event_from_exception(
97+
exception,
98+
client_options=get_client().options,
99+
mechanism={"type": "otlp", "handled": False},
100+
)
101+
102+
capture_event(event, hint=hint)
103+
104+
Span.record_exception = _sentry_patched_record_exception
105+
106+
85107
class SentryOTLPPropagator(SentryPropagator):
86108
"""
87109
We need to override the inject of the older propagator since that
@@ -136,13 +158,28 @@ def _to_traceparent(span_context: "SpanContext") -> str:
136158

137159

138160
class OTLPIntegration(Integration):
161+
"""
162+
Automatically setup OTLP ingestion from the DSN.
163+
164+
:param setup_otlp_traces_exporter: Automatically configure an Exporter to send OTLP traces from the DSN, defaults to True.
165+
Set to False if using a custom collector or to setup the TracerProvider manually.
166+
:param setup_propagator: Automatically configure the Sentry Propagator for Distributed Tracing, defaults to True.
167+
Set to False to configure propagators manually or to disable propagation.
168+
:param capture_exceptions: Intercept and capture exceptions on the OpenTelemetry Span in Sentry as well, defaults to False.
169+
Set to True to turn on capturing but be aware that since Sentry captures most exceptions, there might be double reporting of exceptions.
170+
"""
171+
139172
identifier = "otlp"
140173

141174
def __init__(
142-
self, setup_otlp_traces_exporter: bool = True, setup_propagator: bool = True
175+
self,
176+
setup_otlp_traces_exporter: bool = True,
177+
setup_propagator: bool = True,
178+
capture_exceptions: bool = False,
143179
) -> None:
144180
self.setup_otlp_traces_exporter = setup_otlp_traces_exporter
145181
self.setup_propagator = setup_propagator
182+
self.capture_exceptions = capture_exceptions
146183

147184
@staticmethod
148185
def setup_once() -> None:
@@ -161,3 +198,7 @@ def setup_once_with_options(
161198
logger.debug("[OTLP] Setting up propagator for distributed tracing")
162199
# TODO-neel better propagator support, chain with existing ones if possible instead of replacing
163200
set_global_textmap(SentryOTLPPropagator())
201+
202+
if self.capture_exceptions:
203+
logger.debug("[OTLP] Setting up exception capturing")
204+
setup_capture_exceptions()

0 commit comments

Comments
 (0)