diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index dade93b89f..605970ecbb 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -667,8 +667,17 @@ private SentryId CaptureEvent(SentryEvent evt, SentryHint? hint, Scope scope) { // Event contains a terminal exception -> finish any current transaction as aborted // Do this *after* the event was captured, so that the event is still linked to the transaction. - _options.LogDebug("Ending transaction as Aborted, due to unhandled exception."); - transaction.Finish(SpanStatus.Aborted); + // Skip for OpenTelemetry transactions - these get handled by the SpanProcessor instead. See https://github.com/getsentry/sentry-dotnet/pull/5310 + if (transaction is IBaseTracer { IsOtelInstrumenter: true }) + { + _options.LogDebug( + "Not ending OpenTelemetry transaction as Aborted; it is finished by the SentrySpanProcessor."); + } + else + { + _options.LogDebug("Ending transaction as Aborted, due to unhandled exception."); + transaction.Finish(SpanStatus.Aborted); + } } return id; diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index c669a97060..eea49f6250 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -545,6 +545,36 @@ public void CaptureEvent_TerminalUnhandledException_AbortsActiveTransaction() transaction.IsFinished.Should().BeTrue(); } + [Fact] + public void CaptureEvent_TerminalUnhandledException_DoesNotAbortOpenTelemetryTransaction() + { + // OpenTelemetry-instrumented transactions are owned and finished by the SentrySpanProcessor + // when the underlying Activity ends (which is also where the transaction name, operation and + // otel/response contexts get populated). Finishing them early here would capture them with the + // raw activity name and no otel context. See https://github.com/getsentry/sentry-dotnet/issues/5091 + + // Arrange + _fixture.Options.TracesSampleRate = 1.0; + _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry; + var hub = _fixture.GetSut(); + + var transactionContext = new TransactionContext("test", "operation") + { + Instrumenter = Instrumenter.OpenTelemetry + }; + var transaction = hub.StartTransaction(transactionContext); + hub.ConfigureScope(scope => scope.Transaction = transaction); + + var exception = new Exception("test"); + exception.SetSentryMechanism("test", handled: false, terminal: true); + + // Act + hub.CaptureEvent(new SentryEvent(exception)); + + // Assert + transaction.IsFinished.Should().BeFalse(); + } + [Fact] public void CaptureEvent_NonTerminalUnhandledException_DoesNotAbortActiveTransaction() {