diff --git a/CHANGELOG.md b/CHANGELOG.md index 054dfd7fe2..002c64191c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Unsampled spans no longer propagate empty trace headers ([#4302](https://github.com/getsentry/sentry-dotnet/pull/4302)) + ## 5.11.1 ### Fixes diff --git a/src/Sentry.AspNetCore/Extensions/HttpContextExtensions.cs b/src/Sentry.AspNetCore/Extensions/HttpContextExtensions.cs index 19e4dde3c4..9eb982036a 100644 --- a/src/Sentry.AspNetCore/Extensions/HttpContextExtensions.cs +++ b/src/Sentry.AspNetCore/Extensions/HttpContextExtensions.cs @@ -56,7 +56,14 @@ internal static class HttpContextExtensions try { - return SentryTraceHeader.Parse(value!); + var traceHeader = SentryTraceHeader.Parse(value!); + if (traceHeader?.TraceId != SentryId.Empty) + { + return traceHeader; + } + + options?.LogWarning("Sentry trace header '{0}' has an empty trace ID.", value); + return null; } catch (Exception ex) { diff --git a/src/Sentry/Internal/UnsampledSpan.cs b/src/Sentry/Internal/UnsampledSpan.cs index 3b52bdaa8c..ae64942ef0 100644 --- a/src/Sentry/Internal/UnsampledSpan.cs +++ b/src/Sentry/Internal/UnsampledSpan.cs @@ -6,4 +6,5 @@ internal sealed class UnsampledSpan(UnsampledTransaction transaction, SpanId? sp public override SpanId SpanId { get; } = spanId ?? SpanId.Empty; internal UnsampledTransaction Transaction => transaction; public override ISpan StartChild(string operation) => transaction.StartChild(operation); + public override SentryTraceHeader GetTraceHeader() => transaction.GetTraceHeader(); } diff --git a/test/Sentry.AspNetCore.Tests/Extensions/HttpContextExtensionsTests.cs b/test/Sentry.AspNetCore.Tests/Extensions/HttpContextExtensionsTests.cs index c116976049..339d661a4b 100644 --- a/test/Sentry.AspNetCore.Tests/Extensions/HttpContextExtensionsTests.cs +++ b/test/Sentry.AspNetCore.Tests/Extensions/HttpContextExtensionsTests.cs @@ -78,5 +78,20 @@ public void TryGetRouteTemplate_WithSentryRouteName_RouteName() // Assert Assert.Equal(expectedName, filteredRoute); } + + [Fact] + public void TryGetSentryTraceHeader_WithEmptyTraceId_ReturnsNull() + { + // Arrange + var httpContext = Fixture.GetSut(); + var options = new SentryOptions(); + httpContext.Request.Headers.Append(SentryTraceHeader.HttpHeaderName, "00000000000000000000000000000000-1000000000000000-1"); + + // Act + var header = httpContext.TryGetSentryTraceHeader(options); + + // Assert + Assert.Null(header); + } } #endif diff --git a/test/Sentry.Tests/Internals/UnsampledSpanTests.cs b/test/Sentry.Tests/Internals/UnsampledSpanTests.cs new file mode 100644 index 0000000000..a64edc9eed --- /dev/null +++ b/test/Sentry.Tests/Internals/UnsampledSpanTests.cs @@ -0,0 +1,24 @@ +namespace Sentry.Tests.Internals; + +public class UnsampledSpanTests +{ + [Fact] + public void GetTraceHeader_CreatesHeaderFromUnsampledTransaction() + { + // Arrange + var hub = Substitute.For(); + ITransactionContext context = new TransactionContext("TestTransaction", "TestOperation", + new SentryTraceHeader(SentryId.Create(), SpanId.Create(), false) + ); + var transaction = new UnsampledTransaction(hub, context); + var unsampledSpan = transaction.StartChild("Foo"); + + // Act + var traceHeader = unsampledSpan.GetTraceHeader(); + + // Assert + traceHeader.TraceId.Should().Be(context.TraceId); + traceHeader.SpanId.Should().Be(context.SpanId); + traceHeader.IsSampled.Should().Be(context.IsSampled); + } +}