Skip to content

Investigate OTLP integration #4859

@jamescrosswell

Description

@jamescrosswell

Description

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:

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:

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:

  1. 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?
  1. 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.

Metadata

Metadata

Labels

.NETPull requests that update .net code

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions