-
-
Notifications
You must be signed in to change notification settings - Fork 229
Description
Description
- Developer docs
- Python reference implementation
Note
The Exception Interceptor is something we already have in the POTEL implementation for .NET but have problems with. This will be deprecated in the OTEL APIs. The recommendation is to use the OTEL logging APIs for this instead.
Initial head scratching
Currently the meat of our OTEL integration is implemented in the SentrySpanProcessor. Basically it takes OTEL spans and converts these into Sentry spans, stored on the Sentry scope. We have a couple of different scope implementations: global and AsyncLocal... since Activity.Current (Microsoft's implementation of OTEL) is AsyncLocal, this works best in backend applications. In front end apps using a global scope, often spans from background services can end up interleaved with front end operations.
From what I can gather from the docs and the Python implementation, moving to OTLP would flip this on it's head and we'd instead send Sentry instrumented spans to an OTEL exporter which would pipe both the Sentry spans and any spans from the user's code through to a DSN specific OLTP endpoint on the Sentry server/ingest.
Problems so solve
The SentrySpanProcessor currently ensures OTEL and Sentry spans are parented to one another correctly:
sentry-dotnet/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
Lines 96 to 114 in 54511b5
| if (data.ParentSpanId != default && _map.TryGetValue(data.ParentSpanId, out var mappedParent)) | |
| { | |
| // Explicit ParentSpanId of another activity that we have already mapped | |
| CreateChildSpan(data, mappedParent, data.ParentSpanId); | |
| } | |
| // Note if the current span on the hub is OTel instrumented and is not the parent of `data` then this may be | |
| // intentional (see https://opentelemetry.io/docs/languages/net/instrumentation/#creating-new-root-activities) | |
| // so we explicitly exclude OTel instrumented spans from the following check. | |
| // of Sentry | |
| else if (_hub.GetSpan() is IBaseTracer { IsOtelInstrumenter: false } inferredParent) | |
| { | |
| // When mixing Sentry and OTel instrumentation and we infer that the currently active span is the parent. | |
| var inferredParentSpan = (ISpan)inferredParent; | |
| CreateChildSpan(data, inferredParentSpan, inferredParentSpan.SpanId); | |
| } | |
| else | |
| { | |
| CreateRootSpan(data); | |
| } |
| _hub.ConfigureScope(static (scope, transaction) => scope.Transaction = transaction, transaction); |
It also does a bit of parsing to synthesise errors and enrich the spans:
sentry-dotnet/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
Lines 274 to 280 in 54511b5
| GenerateSentryErrorsFromOtelSpan(data, attributes); | |
| var status = GetSpanStatus(data.Status, attributes); | |
| foreach (var enricher in _enrichers) | |
| { | |
| enricher.Enrich(span, data, _hub, _options); | |
| } |
So I guess a couple of logical questions arise:
- How do we ensure Sentry and OTEL spans are interleaved/parented to one another correctly?
- And how does this work with both backend applications like ASP.NET Core and front end applications like MAUI or WinUI?
- Do we still want/need to do any additional error synthesis or enrichment and, if so, is this also possible when using the OLTP approach?
Why?
Probably the biggest question I have is, "Why would we want to do all of this at all?". For that, ideally we'd have an example of a concrete customer problem that exists when using POTEL (our current approach to OTEL integration) and a clear explanation of how a move to OTLP would solve that... as well as some confidence that moving to OTLP wouldn't trade our current problems for new ones that we don't currently have.