From c354d318d13d8e73af4be2e8719be52b435cae77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 21 Jul 2025 13:48:08 +0200 Subject: [PATCH 01/17] test: normalize ContinueTrace tests --- test/Sentry.Tests/HubTests.cs | 36 +++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 85bf533df9..7cedbddcf2 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1176,7 +1176,7 @@ public void GetBaggage_NoSpanActive_ReturnsBaggageFromPropagationContext() } [Fact] - public void ContinueTrace_SetsPropagationContextAndReturnsTransactionContext() + public void ContinueTrace_ReceivesHeaders_SetsPropagationContextAndReturnsTransactionContext() { // Arrange var hub = _fixture.GetSut(); @@ -1210,6 +1210,25 @@ public void ContinueTrace_SetsPropagationContextAndReturnsTransactionContext() transactionContext.ParentSpanId.Should().Be(SpanId.Parse("2000000000000000")); } + [Fact] + public void ContinueTrace_DoesNotReceiveHeaders_CreatesRootTrace() + { + // Arrange + var hub = _fixture.GetSut(); + + // Act + var transactionContext = hub.ContinueTrace((SentryTraceHeader)null, (BaggageHeader)null, "test-name", "test-operation"); + + // Assert + transactionContext.Name.Should().Be("test-name"); + transactionContext.Operation.Should().Be("test-operation"); + transactionContext.SpanId.Should().NotBeNull(); + transactionContext.ParentSpanId.Should().BeNull(); + transactionContext.TraceId.Should().NotBeNull(); + transactionContext.IsSampled.Should().BeNull(); + transactionContext.IsParentSampled.Should().BeNull(); + } + [Fact] public void ContinueTrace_ReceivesHeadersAsStrings_SetsPropagationContextAndReturnsTransactionContext() { @@ -1239,21 +1258,22 @@ public void ContinueTrace_ReceivesHeadersAsStrings_SetsPropagationContextAndRetu } [Fact] - public void ContinueTrace_DoesNotReceiveHeaders_CreatesRootTrace() + public void ContinueTrace_DoesNotReceiveHeadersAsStrings_CreatesRootTrace() { // Arrange var hub = _fixture.GetSut(); // Act - var transactionContext = hub.ContinueTrace((string)null, null, "test-name"); + var transactionContext = hub.ContinueTrace((string)null, (string)null, "test-name"); // Assert transactionContext.Name.Should().Be("test-name"); - transactionContext.SpanId.Should().NotBe(null); - transactionContext.ParentSpanId.Should().Be(null); - transactionContext.TraceId.Should().NotBe(null); - transactionContext.IsSampled.Should().Be(null); - transactionContext.IsParentSampled.Should().Be(null); + transactionContext.Operation.Should().BeEmpty(); + transactionContext.SpanId.Should().NotBeNull(); + transactionContext.ParentSpanId.Should().BeNull(); + transactionContext.TraceId.Should().NotBeNull(); + transactionContext.IsSampled.Should().BeNull(); + transactionContext.IsParentSampled.Should().BeNull(); } [Fact] From 37908a3ecdb69916acf66ff63ba7cdf656bf899e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:09:00 +0200 Subject: [PATCH 02/17] test: use Assertion over type-cast --- test/Sentry.Tests/HubTests.cs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 7cedbddcf2..687b3a39e7 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -708,8 +708,8 @@ public void StartTransaction_DynamicSamplingContextWithReplayId_UsesActiveReplay var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); // Assert - var transactionTracer = ((TransactionTracer)transaction); - transactionTracer.IsSampled.Should().Be(true); + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.DynamicSamplingContext.Should().NotBeNull(); foreach (var dscItem in dsc!.Items) { @@ -743,7 +743,7 @@ public void StartTransaction_NoDynamicSamplingContext_UsesActiveReplaySessionId( var transaction = hub.StartTransaction(transactionContext, new Dictionary()); // Assert - var transactionTracer = ((TransactionTracer)transaction); + var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.SampleRand.Should().NotBeNull(); transactionTracer.DynamicSamplingContext.Should().NotBeNull(); if (replaySessionIsActive) @@ -770,7 +770,7 @@ public void StartTransaction_NoDynamicSamplingContext_GeneratesSampleRand() var transaction = hub.StartTransaction(transactionContext, customContext); // Assert - var transactionTracer = ((TransactionTracer)transaction); + var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.SampleRand.Should().NotBeNull(); transactionTracer.DynamicSamplingContext.Should().NotBeNull(); transactionTracer.DynamicSamplingContext!.Items.Should().ContainKey("sample_rand"); @@ -789,7 +789,7 @@ public void StartTransaction_DynamicSamplingContextWithoutSampleRand_SampleRandN var transaction = hub.StartTransaction(transactionContext, new Dictionary(), DynamicSamplingContext.Empty); // Assert - var transactionTracer = ((TransactionTracer)transaction); + var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.SampleRand.Should().NotBeNull(); transactionTracer.DynamicSamplingContext.Should().NotBeNull(); // See https://develop.sentry.dev/sdk/telemetry/traces/dynamic-sampling-context/#freezing-dynamic-sampling-context @@ -818,8 +818,8 @@ public void StartTransaction_DynamicSamplingContextWithSampleRand_InheritsSample var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); // Assert - var transactionTracer = ((TransactionTracer)transaction); - transactionTracer.IsSampled.Should().Be(true); + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(0.4); transactionTracer.SampleRand.Should().Be(0.1234); transactionTracer.DynamicSamplingContext.Should().Be(dsc); @@ -851,17 +851,16 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool // Assert if (expectedIsSampled) { - transaction.Should().BeOfType(); - var transactionTracer = ((TransactionTracer)transaction); - transactionTracer.IsSampled.Should().Be(true); + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(sampleRate); transactionTracer.SampleRand.Should().Be(0.1234); transactionTracer.DynamicSamplingContext.Should().Be(dsc); } else { - transaction.Should().BeOfType(); - var unsampledTransaction = ((UnsampledTransaction)transaction); + var unsampledTransaction = transaction.Should().BeOfType().Subject; + unsampledTransaction.IsSampled.Should().BeFalse(); unsampledTransaction.SampleRate.Should().Be(sampleRate); unsampledTransaction.SampleRand.Should().Be(0.1234); unsampledTransaction.DynamicSamplingContext.Should().Be(dsc); @@ -894,17 +893,16 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo // Assert if (expectedIsSampled) { - transaction.Should().BeOfType(); - var transactionTracer = ((TransactionTracer)transaction); - transactionTracer.IsSampled.Should().Be(expectedIsSampled); + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(sampleRate); transactionTracer.SampleRand.Should().Be(0.1234); transactionTracer.DynamicSamplingContext.Should().Be(dsc); } else { - transaction.Should().BeOfType(); - var unsampledTransaction = ((UnsampledTransaction)transaction); + var unsampledTransaction = transaction.Should().BeOfType().Subject; + unsampledTransaction.IsSampled.Should().BeFalse(); unsampledTransaction.SampleRate.Should().Be(sampleRate); unsampledTransaction.SampleRand.Should().Be(0.1234); unsampledTransaction.DynamicSamplingContext.Should().Be(dsc); From 23a45c20af9a9ad3630d67051c807fa77c1a602a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:54:31 +0200 Subject: [PATCH 03/17] fix: DSC contains actual SampleRate --- src/Sentry/DynamicSamplingContext.cs | 18 ++ src/Sentry/Internal/Hub.cs | 7 +- .../SentryTracingMiddlewareTests.cs | 15 +- .../SentrySpanProcessorTests.cs | 3 +- test/Sentry.Tests/HubTests.cs | 170 ++++++++++++++++-- 5 files changed, 186 insertions(+), 27 deletions(-) diff --git a/src/Sentry/DynamicSamplingContext.cs b/src/Sentry/DynamicSamplingContext.cs index 7f1e1b82ba..e81550dadc 100644 --- a/src/Sentry/DynamicSamplingContext.cs +++ b/src/Sentry/DynamicSamplingContext.cs @@ -98,6 +98,24 @@ private DynamicSamplingContext(SentryId traceId, public BaggageHeader ToBaggageHeader() => BaggageHeader.Create(Items, useSentryPrefix: true); + public DynamicSamplingContext WithSampleRate(double sampleRate) + { + if (Items.TryGetValue("sample_rate", out var dscSampleRate)) + { + if (double.TryParse(dscSampleRate, NumberStyles.Float, CultureInfo.InvariantCulture, out var rate)) + { + if (Math.Abs(rate - sampleRate) > double.Epsilon) + { + var items = Items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + items["sample_rate"] = sampleRate.ToString(CultureInfo.InvariantCulture); + return new DynamicSamplingContext(items); + } + } + } + + return this; + } + public DynamicSamplingContext WithReplayId(IReplaySession? replaySession) { if (replaySession?.ActiveReplayId is not { } replayId || replayId == SentryId.Empty) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 67318abc54..2292def14a 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -172,8 +172,8 @@ internal ITransactionTracer StartTransaction( bool? isSampled = null; double? sampleRate = null; - var sampleRand = dynamicSamplingContext?.Items.TryGetValue("sample_rand", out var dscsampleRand) ?? false - ? double.Parse(dscsampleRand, NumberStyles.Float, CultureInfo.InvariantCulture) + var sampleRand = dynamicSamplingContext?.Items.TryGetValue("sample_rand", out var dscSampleRand) ?? false + ? double.Parse(dscSampleRand, NumberStyles.Float, CultureInfo.InvariantCulture) : SampleRandHelper.GenerateSampleRand(context.TraceId.ToString()); // TracesSampler runs regardless of whether a decision has already been made, as it can be used to override it. @@ -196,6 +196,9 @@ internal ITransactionTracer StartTransaction( sampleRate ??= _options.TracesSampleRate ?? 0.0; isSampled ??= context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + // Ensure the actual sampleRate is set on the provided DSC (https://github.com/getsentry/team-sdks/issues/117) + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(sampleRate.Value); + // Make sure there is a replayId (if available) on the provided DSC (if any). dynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession); diff --git a/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs index f20b4cf1c5..3241942048 100644 --- a/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs @@ -316,7 +316,7 @@ public async Task Baggage_header_propagates_to_outbound_requests(bool shouldProp "sentry-trace_id=75302ac48a024bde9a3b3734a82e36c8, " + "sentry-public_key=d4d82fc1c2c4032a83f3a29aa3a3aff, " + "sentry-sample_rand=0.1234, " + - "sentry-sample_rate=0.5"; + "sentry-sample_rate=1"; } else { @@ -395,13 +395,18 @@ public async Task Baggage_header_propagates_to_outbound_requests(bool shouldProp [Fact] public async Task Baggage_header_sets_dynamic_sampling_context() { - // incoming baggage header - const string baggage = + const string incomingBaggageHeader = "sentry-trace_id=75302ac48a024bde9a3b3734a82e36c8, " + "sentry-public_key=d4d82fc1c2c4032a83f3a29aa3a3aff, " + "sentry-sample_rand=0.1234, " + "sentry-sample_rate=0.5"; + const string expectedBaggageHeader = + "sentry-trace_id=75302ac48a024bde9a3b3734a82e36c8, " + + "sentry-public_key=d4d82fc1c2c4032a83f3a29aa3a3aff, " + + "sentry-sample_rand=0.1234, " + + "sentry-sample_rate=1"; + // Arrange TransactionTracer transaction = null; @@ -440,7 +445,7 @@ public async Task Baggage_header_sets_dynamic_sampling_context() { Headers = { - {"baggage", baggage} + {"baggage", incomingBaggageHeader} } }; @@ -449,7 +454,7 @@ public async Task Baggage_header_sets_dynamic_sampling_context() // Assert var dsc = transaction?.DynamicSamplingContext; Assert.NotNull(dsc); - Assert.Equal(baggage, dsc.ToBaggageHeader().ToString()); + Assert.Equal(expectedBaggageHeader, dsc.ToBaggageHeader().ToString()); } [Fact] diff --git a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs index 360d3a6eed..c97e15f6a6 100644 --- a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs +++ b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs @@ -141,7 +141,8 @@ public void OnStart_Transaction_With_DynamicSamplingContext() { actual.Items["trace_id"].Should().Be(expected["trace_id"]); actual.Items["public_key"].Should().Be(expected["public_key"]); - actual.Items["sample_rate"].Should().Be(expected["sample_rate"]); + actual.Items["sample_rate"].Should().NotBe(expected["sample_rate"]); + actual.Items["sample_rate"].Should().Be(_fixture.Options.TracesSampleRate.ToString()); } } diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 687b3a39e7..3441e02ca0 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -681,6 +681,76 @@ public void StartTransaction_SameInstrumenter_SampledIn() transaction.IsSampled.Should().BeTrue(); } + [Fact] + public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate() + { + // Arrange + var transactionContext = new TransactionContext("name", "operation"); + var dsc = BaggageHeader.Create(new List> + { + {"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"}, + {"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}, + {"sentry-sample_rate", "0.5"}, + {"sentry-sample_rand", "0.1234"}, + }).CreateDynamicSamplingContext(); + + _fixture.Options.TracesSampler = _ => 0.5; + _fixture.Options.TracesSampleRate = 0.5; + + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); + + // Assert + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.SampleRate.Should().Be(0.5); + transactionTracer.DynamicSamplingContext.Should().BeSameAs(dsc); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesActualSampleRate(bool useTracesSampler, bool useTracesSampleRate) + { + // Arrange + var transactionContext = new TransactionContext("name", "operation"); + var dsc = BaggageHeader.Create(new List> + { + {"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"}, + {"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}, + {"sentry-sample_rate", "0.5"}, + {"sentry-sample_rand", "0.1234"}, + }).CreateDynamicSamplingContext(); + + _fixture.Options.TracesSampler = useTracesSampler ? _ => 0.3 : _ => null; + _fixture.Options.TracesSampleRate = useTracesSampleRate ? 0.4 : null; + + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); + + // Assert + if (useTracesSampler || useTracesSampleRate) + { + var expectedSampleRate = useTracesSampler ? 0.3 : 0.4; + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.SampleRate.Should().Be(expectedSampleRate); + transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + } + else + { + var unsampledTransaction = transaction.Should().BeOfType().Subject; + unsampledTransaction.SampleRate.Should().Be(0.0); + unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(0)); + } + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -711,22 +781,15 @@ public void StartTransaction_DynamicSamplingContextWithReplayId_UsesActiveReplay var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.DynamicSamplingContext.Should().NotBeNull(); - foreach (var dscItem in dsc!.Items) + + var expectedDsc = dsc.ReplaceSampleRate(_fixture.Options.TracesSampleRate.Value); + if (replaySessionIsActive) { - if (dscItem.Key == "replay_id") - { - transactionTracer.DynamicSamplingContext!.Items["replay_id"].Should().Be(replaySessionIsActive - // We overwrite the replay_id when we have an active replay session - ? _fixture.ReplaySession.ActiveReplayId.ToString() - // Otherwise we propagate whatever was in the baggage header - : dscItem.Value); - } - else - { - transactionTracer.DynamicSamplingContext!.Items.Should() - .Contain(kvp => kvp.Key == dscItem.Key && kvp.Value == dscItem.Value); - } + // We overwrite the replay_id when we have an active replay session + // Otherwise we propagate whatever was in the baggage header + expectedDsc = expectedDsc.ReplaceReplayId(_fixture.ReplaySession); } + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(expectedDsc); } [Theory] @@ -822,7 +885,8 @@ public void StartTransaction_DynamicSamplingContextWithSampleRand_InheritsSample transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(0.4); transactionTracer.SampleRand.Should().Be(0.1234); - transactionTracer.DynamicSamplingContext.Should().Be(dsc); + transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(0.4)); } [Theory] @@ -855,7 +919,8 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(sampleRate); transactionTracer.SampleRand.Should().Be(0.1234); - transactionTracer.DynamicSamplingContext.Should().Be(dsc); + transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); } else { @@ -863,7 +928,8 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool unsampledTransaction.IsSampled.Should().BeFalse(); unsampledTransaction.SampleRate.Should().Be(sampleRate); unsampledTransaction.SampleRand.Should().Be(0.1234); - unsampledTransaction.DynamicSamplingContext.Should().Be(dsc); + unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); } } @@ -897,7 +963,8 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo transactionTracer.IsSampled.Should().BeTrue(); transactionTracer.SampleRate.Should().Be(sampleRate); transactionTracer.SampleRand.Should().Be(0.1234); - transactionTracer.DynamicSamplingContext.Should().Be(dsc); + transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); } else { @@ -905,7 +972,8 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo unsampledTransaction.IsSampled.Should().BeFalse(); unsampledTransaction.SampleRate.Should().Be(sampleRate); unsampledTransaction.SampleRand.Should().Be(0.1234); - unsampledTransaction.DynamicSamplingContext.Should().Be(dsc); + unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(sampleRate)); } } @@ -2108,3 +2176,67 @@ internal partial class HubTestsJsonContext : JsonSerializerContext { } #endif + +#nullable enable +file static class DynamicSamplingContextExtensions +{ + public static DynamicSamplingContext ReplaceSampleRate(this DynamicSamplingContext? dsc, double sampleRate) + { + Assert.NotNull(dsc); + + var value = sampleRate.ToString(CultureInfo.InvariantCulture); + return dsc.Replace("sentry-sample_rate", value); + } + + public static DynamicSamplingContext ReplaceReplayId(this DynamicSamplingContext? dsc, IReplaySession replaySession) + { + Assert.NotNull(dsc); + + if (!replaySession.ActiveReplayId.HasValue) + { + throw new InvalidOperationException($"No {nameof(IReplaySession.ActiveReplayId)}."); + } + + var value = replaySession.ActiveReplayId.Value.ToString(); + return dsc.Replace("sentry-replay_id", value); + } + + private static DynamicSamplingContext Replace(this DynamicSamplingContext dsc, string key, string newValue) + { + var items = dsc.ToBaggageHeader().Members.ToList(); + var index = items.FindSingleIndex(key); + + var oldValue = items[index].Value; + if (oldValue == newValue) + { + throw new InvalidOperationException($"{key} already is {oldValue}."); + } + + items[index] = new KeyValuePair(key, newValue); + + var baggage = BaggageHeader.Create(items); + var dynamicSamplingContext = DynamicSamplingContext.CreateFromBaggageHeader(baggage, null); + if (dynamicSamplingContext is null) + { + throw new InvalidOperationException($"Invalid {nameof(BaggageHeader)}: {baggage}"); + } + + return dynamicSamplingContext; + } + + private static int FindSingleIndex(this List> items, string key) + { + var index = items.FindIndex(item => item.Key == key); + if (index == -1) + { + throw new InvalidOperationException($"{key} not found."); + } + + if (items.FindLastIndex(item => item.Key == key) != index) + { + throw new InvalidOperationException($"Duplicate {key} found."); + } + + return index; + } +} From 5cace1fd23cc00337005f7bcbe346f2176773c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:31:02 +0200 Subject: [PATCH 04/17] test: fix catch and handle failed Assertions --- test/Sentry.Tests/HubTests.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 3441e02ca0..a4a9c4f031 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -66,13 +66,13 @@ public void PushAndLockScope_DoesNotAffectOuterScope() var hub = _fixture.GetSut(); // Act & assert - hub.ConfigureScope(s => Assert.False(s.Locked)); + hub.ScopeManager.ConfigureScope(s => Assert.False(s.Locked)); using (hub.PushAndLockScope()) { - hub.ConfigureScope(s => Assert.True(s.Locked)); + hub.ScopeManager.ConfigureScope(s => Assert.True(s.Locked)); } - hub.ConfigureScope(s => Assert.False(s.Locked)); + hub.ScopeManager.ConfigureScope(s => Assert.False(s.Locked)); } [Fact] @@ -1259,17 +1259,18 @@ public void ContinueTrace_ReceivesHeaders_SetsPropagationContextAndReturnsTransa {"sentry-sample_rate", "1.0"} }); - hub.ConfigureScope(scope => scope.PropagationContext.TraceId.Should().Be("43365712692146d08ee11a729dfbcaca")); // Sanity check + hub.ScopeManager.ConfigureScope(scope => scope.PropagationContext.TraceId.Should().Be(SentryId.Parse("43365712692146d08ee11a729dfbcaca"))); // Sanity check // Act var transactionContext = hub.ContinueTrace(traceHeader, baggageHeader, "test-name"); // Assert - hub.ConfigureScope(scope => + hub.ScopeManager.ConfigureScope(scope => { scope.PropagationContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); scope.PropagationContext.ParentSpanId.Should().Be(SpanId.Parse("2000000000000000")); Assert.NotNull(scope.PropagationContext._dynamicSamplingContext); + scope.PropagationContext._dynamicSamplingContext.Items.Should().Contain(baggageHeader.GetSentryMembers()); }); transactionContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); @@ -1306,17 +1307,18 @@ public void ContinueTrace_ReceivesHeadersAsStrings_SetsPropagationContextAndRetu var traceHeader = "5bd5f6d346b442dd9177dce9302fd737-2000000000000000"; var baggageHeader = "sentry-trace_id=5bd5f6d346b442dd9177dce9302fd737, sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=1.0"; - hub.ConfigureScope(scope => scope.PropagationContext.TraceId.Should().Be("43365712692146d08ee11a729dfbcaca")); // Sanity check + hub.ScopeManager.ConfigureScope(scope => scope.PropagationContext.TraceId.Should().Be(SentryId.Parse("43365712692146d08ee11a729dfbcaca"))); // Sanity check // Act var transactionContext = hub.ContinueTrace(traceHeader, baggageHeader, "test-name"); // Assert - hub.ConfigureScope(scope => + hub.ScopeManager.ConfigureScope(scope => { scope.PropagationContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); scope.PropagationContext.ParentSpanId.Should().Be(SpanId.Parse("2000000000000000")); Assert.NotNull(scope.PropagationContext._dynamicSamplingContext); + scope.PropagationContext._dynamicSamplingContext.ToBaggageHeader().Members.Should().Contain(BaggageHeader.TryParse(baggageHeader)!.Members); }); transactionContext.TraceId.Should().Be(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737")); @@ -1355,7 +1357,7 @@ public void CaptureTransaction_AfterTransactionFinishes_ResetsTransactionOnScope transaction.Finish(); // Assert - hub.ConfigureScope(scope => scope.Transaction.Should().BeNull()); + hub.ScopeManager.ConfigureScope(scope => scope.Transaction.Should().BeNull()); } #nullable enable From 4401a9808f6c845f3daf22b884a1447bd8ff7f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:31:22 +0200 Subject: [PATCH 05/17] style: reformat HubTests --- test/Sentry.Tests/HubTests.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index a4a9c4f031..00c7fc0192 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -173,14 +173,13 @@ public void CaptureException_TransactionFinished_Gets_DSC_From_LinkedSpan() {"sentry-sample_rate", "1.0"}, {"sentry-trace_id", "75302ac48a024bde9a3b3734a82e36c8"}, {"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}, - {"sentry-replay_id","bfd31b89a59d41c99d96dc2baf840ecd"} + {"sentry-replay_id", "bfd31b89a59d41c99d96dc2baf840ecd"} }).CreateDynamicSamplingContext(_fixture.ReplaySession); var transaction = hub.StartTransaction( transactionContext, new Dictionary(), - dsc - ); + dsc); transaction.Finish(exception); // Act @@ -767,7 +766,7 @@ public void StartTransaction_DynamicSamplingContextWithReplayId_UsesActiveReplay {"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}, {"sentry-sampled", "true"}, {"sentry-sample_rate", "0.5"}, // Required in the baggage header, but ignored by sampling logic - {"sentry-replay_id","bfd31b89a59d41c99d96dc2baf840ecd"} + {"sentry-replay_id", "bfd31b89a59d41c99d96dc2baf840ecd"} }).CreateDynamicSamplingContext(dummyReplaySession); _fixture.Options.TracesSampleRate = 1.0; From 3197ab6139fbe9f1369e179cf07d922aab323f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:30:19 +0200 Subject: [PATCH 06/17] test: fix HubTests --- test/Sentry.Tests/HubTests.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 00c7fc0192..99679c5fd5 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1218,9 +1218,13 @@ public void GetBaggage_SpanActive_ReturnsBaggageFromSpan(bool isSampled) // Assert baggage.Should().NotBeNull(); - Assert.Equal("43365712692146d08ee11a729dfbcaca", Assert.Contains("trace_id", dsc.Items)); - Assert.Equal("d4d82fc1c2c4032a83f3a29aa3a3aff", Assert.Contains("public_key", dsc.Items)); - Assert.Equal("0.0", Assert.Contains("sample_rate", dsc.Items)); + var sampleRand = isSampled ? ((TransactionTracer)transaction).SampleRand : ((UnsampledTransaction)transaction).SampleRand; + baggage.Members.Should().Equal([ + new KeyValuePair("sentry-trace_id", "43365712692146d08ee11a729dfbcaca"), + new KeyValuePair("sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"), + new KeyValuePair("sentry-sample_rate", isSampled ? "1" : "0.0"), + new KeyValuePair("sentry-sample_rand", sampleRand!.Value.ToString(CultureInfo.InvariantCulture)), + ]); } [Fact] @@ -1237,7 +1241,7 @@ public void GetBaggage_NoSpanActive_ReturnsBaggageFromPropagationContext() // Assert baggage.Should().NotBeNull(); - Assert.Contains("43365712692146d08ee11a729dfbcaca", baggage!.ToString()); + Assert.Contains("sentry-trace_id=43365712692146d08ee11a729dfbcaca", baggage!.ToString()); } [Fact] @@ -1286,6 +1290,12 @@ public void ContinueTrace_DoesNotReceiveHeaders_CreatesRootTrace() var transactionContext = hub.ContinueTrace((SentryTraceHeader)null, (BaggageHeader)null, "test-name", "test-operation"); // Assert + hub.ScopeManager.ConfigureScope(scope => + { + Assert.Null(scope.PropagationContext.ParentSpanId); + Assert.Null(scope.PropagationContext._dynamicSamplingContext); + }); + transactionContext.Name.Should().Be("test-name"); transactionContext.Operation.Should().Be("test-operation"); transactionContext.SpanId.Should().NotBeNull(); @@ -1334,6 +1344,12 @@ public void ContinueTrace_DoesNotReceiveHeadersAsStrings_CreatesRootTrace() var transactionContext = hub.ContinueTrace((string)null, (string)null, "test-name"); // Assert + hub.ScopeManager.ConfigureScope(scope => + { + Assert.Null(scope.PropagationContext.ParentSpanId); + Assert.Null(scope.PropagationContext._dynamicSamplingContext); + }); + transactionContext.Name.Should().Be("test-name"); transactionContext.Operation.Should().BeEmpty(); transactionContext.SpanId.Should().NotBeNull(); From bc91dd3bf6cf844e239ac429892d95b9ce00a21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:47:20 +0200 Subject: [PATCH 07/17] docs: update CHANGELOG.md --- CHANGELOG.md | 3 ++- src/Sentry/Internal/Hub.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b899e623..2ca81f2e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- Overwrite `sample_rate` of _Dynamic Sampling Context (DSC)_ when making sampling decisions ([#4374](https://github.com/getsentry/sentry-dotnet/pull/4374)) - Native AOT: don't load SentryNative on unsupported platforms ([#4347](https://github.com/getsentry/sentry-dotnet/pull/4347)) ### Dependencies @@ -1244,7 +1245,7 @@ There are some functional differences when publishing Native AOT: ### Fixes -- Resolved an isse where the SDK would throw an exception while attempting to set the DynamicSamplingContext but the context exists already. ([#2592](https://github.com/getsentry/sentry-dotnet/pull/2592)) +- Resolved an issue where the SDK would throw an exception while attempting to set the DynamicSamplingContext but the context exists already. ([#2592](https://github.com/getsentry/sentry-dotnet/pull/2592)) ### Dependencies diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 2292def14a..e4f60272f2 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -196,7 +196,7 @@ internal ITransactionTracer StartTransaction( sampleRate ??= _options.TracesSampleRate ?? 0.0; isSampled ??= context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); - // Ensure the actual sampleRate is set on the provided DSC (https://github.com/getsentry/team-sdks/issues/117) + // Ensure the actual sampleRate is set on the provided DSC (https://github.com/getsentry/sentry-dotnet/pull/4374) dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(sampleRate.Value); // Make sure there is a replayId (if available) on the provided DSC (if any). From 755bbef292827032bfd6d43712518d4bf3559f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:34:37 +0200 Subject: [PATCH 08/17] test: skip failing test on Android & iOS (device tests) --- test/Sentry.Tests/HubTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 99679c5fd5..6077a25231 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -59,13 +59,17 @@ public void PushScope_BreadcrumbWithinScope_NotVisibleOutside() Assert.Empty(hub.ScopeManager.GetCurrent().Key.Breadcrumbs); } - [Fact] + [SkippableFact] public void PushAndLockScope_DoesNotAffectOuterScope() { +#if __ANDROID__ || __IOS__ + Skip.If(true, "Fails on both Android & iOS Device Tests"); +#endif + // Arrange var hub = _fixture.GetSut(); - // Act & assert + // Act & Assert hub.ScopeManager.ConfigureScope(s => Assert.False(s.Locked)); using (hub.PushAndLockScope()) { From 62592339098ef1d91a8d71403a622f1805177ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 24 Jul 2025 12:04:47 +0200 Subject: [PATCH 09/17] fix: DSC sample_rate when sampling forced --- src/Sentry/DynamicSamplingContext.cs | 13 +++++++--- src/Sentry/Internal/Hub.cs | 2 +- test/Sentry.Tests/HubTests.cs | 37 +++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Sentry/DynamicSamplingContext.cs b/src/Sentry/DynamicSamplingContext.cs index e81550dadc..f11d12e375 100644 --- a/src/Sentry/DynamicSamplingContext.cs +++ b/src/Sentry/DynamicSamplingContext.cs @@ -98,16 +98,23 @@ private DynamicSamplingContext(SentryId traceId, public BaggageHeader ToBaggageHeader() => BaggageHeader.Create(Items, useSentryPrefix: true); - public DynamicSamplingContext WithSampleRate(double sampleRate) + public DynamicSamplingContext WithSampleRate(bool? isSampled, double sampleRate) { if (Items.TryGetValue("sample_rate", out var dscSampleRate)) { if (double.TryParse(dscSampleRate, NumberStyles.Float, CultureInfo.InvariantCulture, out var rate)) { - if (Math.Abs(rate - sampleRate) > double.Epsilon) + var newSampleRate = isSampled switch + { + true => 1.0, + false => 0.0, + _ => sampleRate, + }; + + if (Math.Abs(rate - newSampleRate) > double.Epsilon) { var items = Items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - items["sample_rate"] = sampleRate.ToString(CultureInfo.InvariantCulture); + items["sample_rate"] = newSampleRate.ToString(CultureInfo.InvariantCulture); return new DynamicSamplingContext(items); } } diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index e4f60272f2..4aacc861b6 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -197,7 +197,7 @@ internal ITransactionTracer StartTransaction( isSampled ??= context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); // Ensure the actual sampleRate is set on the provided DSC (https://github.com/getsentry/sentry-dotnet/pull/4374) - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(sampleRate.Value); + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(context.IsSampled, sampleRate.Value); // Make sure there is a replayId (if available) on the provided DSC (if any). dynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 6077a25231..43b8bd0e4a 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -63,7 +63,7 @@ public void PushScope_BreadcrumbWithinScope_NotVisibleOutside() public void PushAndLockScope_DoesNotAffectOuterScope() { #if __ANDROID__ || __IOS__ - Skip.If(true, "Fails on both Android & iOS Device Tests"); + Skip.If(true, "Fails on both Android & iOS Device Tests. See https://github.com/getsentry/sentry-dotnet/issues/4385."); #endif // Arrange @@ -754,6 +754,41 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesActualSamp } } + [SkippableTheory] + [InlineData(null, 0.3)] + [InlineData(true, 1.0)] + [InlineData(false, 0.0)] + public void StartTransaction_DynamicSamplingContextWithForcedSampling_UsesForcedValue(bool? isSampled, double expectedSampleRate) + { +#if DEBUG + Skip.If(isSampled is false, "Should return an 'UnsampledTransaction', not a 'TransactionTracer'. See https://github.com/getsentry/sentry-dotnet/issues/4386."); +#endif + + // Arrange + var transactionContext = new TransactionContext("name", "operation", isSampled: isSampled); + var dsc = BaggageHeader.Create(new List> + { + {"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"}, + {"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}, + {"sentry-sample_rate", "0.5"}, + {"sentry-sample_rand", "0.1234"}, + }).CreateDynamicSamplingContext(); + + _fixture.Options.TracesSampler = _ => 0.3; + _fixture.Options.TracesSampleRate = 0.4; + + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); + + // Assert + var transactionTracer = transaction.Should().BeOfType().Subject; + transactionTracer.SampleRate.Should().Be(0.3); + transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + } + [Theory] [InlineData(true)] [InlineData(false)] From fd39406b1e0a298c3eceac93c35b99abd26f07fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 24 Jul 2025 12:24:08 +0200 Subject: [PATCH 10/17] test: fix PushAndLockScope when GlobalMode --- test/Sentry.Tests/HubTests.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 43b8bd0e4a..77c4726b8a 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -59,13 +59,9 @@ public void PushScope_BreadcrumbWithinScope_NotVisibleOutside() Assert.Empty(hub.ScopeManager.GetCurrent().Key.Breadcrumbs); } - [SkippableFact] + [Fact] public void PushAndLockScope_DoesNotAffectOuterScope() { -#if __ANDROID__ || __IOS__ - Skip.If(true, "Fails on both Android & iOS Device Tests. See https://github.com/getsentry/sentry-dotnet/issues/4385."); -#endif - // Arrange var hub = _fixture.GetSut(); @@ -76,7 +72,14 @@ public void PushAndLockScope_DoesNotAffectOuterScope() hub.ScopeManager.ConfigureScope(s => Assert.True(s.Locked)); } - hub.ScopeManager.ConfigureScope(s => Assert.False(s.Locked)); + if (_fixture.Options.IsGlobalModeEnabled) + { + hub.ScopeManager.ConfigureScope(s => Assert.True(s.Locked)); + } + else + { + hub.ScopeManager.ConfigureScope(s => Assert.False(s.Locked)); + } } [Fact] From cd5355b598ad65ec722d613979b5db81993018d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:09:46 +0200 Subject: [PATCH 11/17] fix: forced sampling is only considered when TracesSampler has not decided --- src/Sentry/Internal/Hub.cs | 15 +++++-- test/Sentry.Tests/HubTests.cs | 75 ++++++++++++----------------------- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 925383319d..8313129137 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -189,16 +189,23 @@ internal ITransactionTracer StartTransaction( // The TracesSampler trumps all other sampling decisions (even the trace header) sampleRate = samplerSampleRate; isSampled = SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + + // Ensure the actual sampleRate is set on the provided DSC (if any) + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(null, sampleRate.Value); } } // If the sampling decision isn't made by a trace sampler we check the trace header first (from the context) or // finally fallback to Random sampling if the decision has been made by no other means - sampleRate ??= _options.TracesSampleRate ?? 0.0; - isSampled ??= context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + if (isSampled == null) + { + Debug.Assert(sampleRate == null); + sampleRate = _options.TracesSampleRate ?? 0.0; + isSampled = context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); - // Ensure the actual sampleRate is set on the provided DSC (https://github.com/getsentry/sentry-dotnet/pull/4374) - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(context.IsSampled, sampleRate.Value); + // Ensure the actual sampleRate is set on the provided DSC (if any) + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(context.IsSampled, sampleRate.Value); + } // Make sure there is a replayId (if available) on the provided DSC (if any). dynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 5b1853b330..8ada4deb8d 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -714,15 +714,28 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate transactionTracer.DynamicSamplingContext.Should().BeSameAs(dsc); } - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesActualSampleRate(bool useTracesSampler, bool useTracesSampleRate) + [SkippableTheory] + [InlineData(null, 0.3, 0.4, true, 0.3)] + [InlineData(null, 0.3, null, true, 0.3)] + [InlineData(null, null, 0.4, true, 0.4)] + [InlineData(null, null, null, false, 0.0)] + [InlineData(true, 0.3, 0.4, true, 0.3)] + [InlineData(true, 0.3, null, true, 0.3)] + [InlineData(true, null, 0.4, true, 0.4, 1.0)] + [InlineData(true, null, null, true, 0.0, 1.0)] + [InlineData(false, 0.3, 0.4, true, 0.3)] + [InlineData(false, 0.3, null, true, 0.3)] + [InlineData(false, null, 0.4, false, 0.4, 0.0)] + [InlineData(false, null, null, false, 0.0, 0.0)] + public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSampleRate(bool? isSampled, double? tracesSampler, double? tracesSampleRate, bool expectedIsSampled, double expectedSampleRate, double? expectedDscSampleRate = null) { +#if DEBUG + Skip.If(isSampled is false && expectedIsSampled, $"Forced sampling via {nameof(ITransactionContext.IsSampled)} is not considered when {nameof(SentryOptions.TracesSampler)} is used, resulting in `Debug.Assert` of {nameof(TransactionTracer)} `.ctor`."); +#endif + expectedDscSampleRate ??= expectedSampleRate; + // Arrange - var transactionContext = new TransactionContext("name", "operation"); + var transactionContext = new TransactionContext("name", "operation", isSampled: isSampled); var dsc = BaggageHeader.Create(new List> { {"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"}, @@ -731,8 +744,8 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesActualSamp {"sentry-sample_rand", "0.1234"}, }).CreateDynamicSamplingContext(); - _fixture.Options.TracesSampler = useTracesSampler ? _ => 0.3 : _ => null; - _fixture.Options.TracesSampleRate = useTracesSampleRate ? 0.4 : null; + _fixture.Options.TracesSampler = _ => tracesSampler; + _fixture.Options.TracesSampleRate = tracesSampleRate; var hub = _fixture.GetSut(); @@ -740,58 +753,22 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesActualSamp var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); // Assert - if (useTracesSampler || useTracesSampleRate) + if (expectedIsSampled) { - var expectedSampleRate = useTracesSampler ? 0.3 : 0.4; var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.SampleRate.Should().Be(expectedSampleRate); transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedDscSampleRate.Value)); } else { var unsampledTransaction = transaction.Should().BeOfType().Subject; - unsampledTransaction.SampleRate.Should().Be(0.0); + unsampledTransaction.SampleRate.Should().Be(expectedSampleRate); unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); - unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(0)); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedDscSampleRate.Value)); } } - [SkippableTheory] - [InlineData(null, 0.3)] - [InlineData(true, 1.0)] - [InlineData(false, 0.0)] - public void StartTransaction_DynamicSamplingContextWithForcedSampling_UsesForcedValue(bool? isSampled, double expectedSampleRate) - { -#if DEBUG - Skip.If(isSampled is false, "Should return an 'UnsampledTransaction', not a 'TransactionTracer'. See https://github.com/getsentry/sentry-dotnet/issues/4386."); -#endif - - // Arrange - var transactionContext = new TransactionContext("name", "operation", isSampled: isSampled); - var dsc = BaggageHeader.Create(new List> - { - {"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"}, - {"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}, - {"sentry-sample_rate", "0.5"}, - {"sentry-sample_rand", "0.1234"}, - }).CreateDynamicSamplingContext(); - - _fixture.Options.TracesSampler = _ => 0.3; - _fixture.Options.TracesSampleRate = 0.4; - - var hub = _fixture.GetSut(); - - // Act - var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc); - - // Assert - var transactionTracer = transaction.Should().BeOfType().Subject; - transactionTracer.SampleRate.Should().Be(0.3); - transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); - } - [Theory] [InlineData(true)] [InlineData(false)] From 8dbe5e36d47c74794fe2aec5a4345ed3c6a1b665 Mon Sep 17 00:00:00 2001 From: Flash0ver <38893694+Flash0ver@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:01:03 +0200 Subject: [PATCH 12/17] fix: no longer overwrite DSC sample_rate when upstream sampling decision has been made --- src/Sentry/DynamicSamplingContext.cs | 13 +++---------- src/Sentry/Internal/Hub.cs | 15 ++++----------- test/Sentry.Tests/HubTests.cs | 15 +++++++-------- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/Sentry/DynamicSamplingContext.cs b/src/Sentry/DynamicSamplingContext.cs index f11d12e375..e81550dadc 100644 --- a/src/Sentry/DynamicSamplingContext.cs +++ b/src/Sentry/DynamicSamplingContext.cs @@ -98,23 +98,16 @@ private DynamicSamplingContext(SentryId traceId, public BaggageHeader ToBaggageHeader() => BaggageHeader.Create(Items, useSentryPrefix: true); - public DynamicSamplingContext WithSampleRate(bool? isSampled, double sampleRate) + public DynamicSamplingContext WithSampleRate(double sampleRate) { if (Items.TryGetValue("sample_rate", out var dscSampleRate)) { if (double.TryParse(dscSampleRate, NumberStyles.Float, CultureInfo.InvariantCulture, out var rate)) { - var newSampleRate = isSampled switch - { - true => 1.0, - false => 0.0, - _ => sampleRate, - }; - - if (Math.Abs(rate - newSampleRate) > double.Epsilon) + if (Math.Abs(rate - sampleRate) > double.Epsilon) { var items = Items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - items["sample_rate"] = newSampleRate.ToString(CultureInfo.InvariantCulture); + items["sample_rate"] = sampleRate.ToString(CultureInfo.InvariantCulture); return new DynamicSamplingContext(items); } } diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 8313129137..b2af31ef78 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -189,23 +189,16 @@ internal ITransactionTracer StartTransaction( // The TracesSampler trumps all other sampling decisions (even the trace header) sampleRate = samplerSampleRate; isSampled = SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); - - // Ensure the actual sampleRate is set on the provided DSC (if any) - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(null, sampleRate.Value); } } // If the sampling decision isn't made by a trace sampler we check the trace header first (from the context) or // finally fallback to Random sampling if the decision has been made by no other means - if (isSampled == null) - { - Debug.Assert(sampleRate == null); - sampleRate = _options.TracesSampleRate ?? 0.0; - isSampled = context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + sampleRate ??= _options.TracesSampleRate ?? 0.0; + isSampled ??= context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); - // Ensure the actual sampleRate is set on the provided DSC (if any) - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(context.IsSampled, sampleRate.Value); - } + // Ensure the actual sampleRate is set on the provided DSC (if any) + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(sampleRate.Value); // Make sure there is a replayId (if available) on the provided DSC (if any). dynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 8ada4deb8d..aa3c0d5d68 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -721,18 +721,17 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate [InlineData(null, null, null, false, 0.0)] [InlineData(true, 0.3, 0.4, true, 0.3)] [InlineData(true, 0.3, null, true, 0.3)] - [InlineData(true, null, 0.4, true, 0.4, 1.0)] - [InlineData(true, null, null, true, 0.0, 1.0)] + [InlineData(true, null, 0.4, true, 0.4)] + [InlineData(true, null, null, true, 0.0)] [InlineData(false, 0.3, 0.4, true, 0.3)] [InlineData(false, 0.3, null, true, 0.3)] - [InlineData(false, null, 0.4, false, 0.4, 0.0)] - [InlineData(false, null, null, false, 0.0, 0.0)] - public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSampleRate(bool? isSampled, double? tracesSampler, double? tracesSampleRate, bool expectedIsSampled, double expectedSampleRate, double? expectedDscSampleRate = null) + [InlineData(false, null, 0.4, false, 0.4)] + [InlineData(false, null, null, false, 0.0)] + public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSampleRate(bool? isSampled, double? tracesSampler, double? tracesSampleRate, bool expectedIsSampled, double expectedSampleRate) { #if DEBUG Skip.If(isSampled is false && expectedIsSampled, $"Forced sampling via {nameof(ITransactionContext.IsSampled)} is not considered when {nameof(SentryOptions.TracesSampler)} is used, resulting in `Debug.Assert` of {nameof(TransactionTracer)} `.ctor`."); #endif - expectedDscSampleRate ??= expectedSampleRate; // Arrange var transactionContext = new TransactionContext("name", "operation", isSampled: isSampled); @@ -758,14 +757,14 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSamp var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.SampleRate.Should().Be(expectedSampleRate); transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedDscSampleRate.Value)); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); } else { var unsampledTransaction = transaction.Should().BeOfType().Subject; unsampledTransaction.SampleRate.Should().Be(expectedSampleRate); unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); - unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedDscSampleRate.Value)); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); } } From fa43554acb70f8d7886ed5b974879a17ec80213b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:10:41 +0200 Subject: [PATCH 13/17] fix: overwrite DSC sample_rate only when TracesSampler or TracesSampleRate reached a sampling decision --- src/Sentry/Internal/Hub.cs | 20 +++++++++++---- test/Sentry.Tests/HubTests.cs | 48 ++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index b2af31ef78..f5f7e7a7c8 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -188,17 +188,27 @@ internal ITransactionTracer StartTransaction( { // The TracesSampler trumps all other sampling decisions (even the trace header) sampleRate = samplerSampleRate; - isSampled = SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + isSampled = SampleRandHelper.IsSampled(sampleRand, samplerSampleRate); + + // Ensure the actual sampleRate is set on the provided DSC (if any) when the TracesSampler reached a sampling decision + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(samplerSampleRate); } } // If the sampling decision isn't made by a trace sampler we check the trace header first (from the context) or // finally fallback to Random sampling if the decision has been made by no other means - sampleRate ??= _options.TracesSampleRate ?? 0.0; - isSampled ??= context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); + if (sampleRate == null) + { + Debug.Assert(isSampled == null); + sampleRate = _options.TracesSampleRate ?? 0.0; + isSampled = context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); - // Ensure the actual sampleRate is set on the provided DSC (if any) - dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(sampleRate.Value); + if (context.IsSampled is null && _options.TracesSampleRate is not null) + { + // Ensure the actual sampleRate is set on the provided DSC (if any) when not IsSampled upstream but the TracesSampleRate reached a sampling decision + dynamicSamplingContext = dynamicSamplingContext?.WithSampleRate(_options.TracesSampleRate.Value); + } + } // Make sure there is a replayId (if available) on the provided DSC (if any). dynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index aa3c0d5d68..6c3b78ddd8 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -715,19 +715,19 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate } [SkippableTheory] - [InlineData(null, 0.3, 0.4, true, 0.3)] - [InlineData(null, 0.3, null, true, 0.3)] - [InlineData(null, null, 0.4, true, 0.4)] - [InlineData(null, null, null, false, 0.0)] - [InlineData(true, 0.3, 0.4, true, 0.3)] - [InlineData(true, 0.3, null, true, 0.3)] - [InlineData(true, null, 0.4, true, 0.4)] - [InlineData(true, null, null, true, 0.0)] - [InlineData(false, 0.3, 0.4, true, 0.3)] - [InlineData(false, 0.3, null, true, 0.3)] - [InlineData(false, null, 0.4, false, 0.4)] - [InlineData(false, null, null, false, 0.0)] - public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSampleRate(bool? isSampled, double? tracesSampler, double? tracesSampleRate, bool expectedIsSampled, double expectedSampleRate) + [InlineData(null, 0.3, 0.4, true, 0.3, true)] + [InlineData(null, 0.3, null, true, 0.3, true)] + [InlineData(null, null, 0.4, true, 0.4, true)] + [InlineData(null, null, null, false, 0.0, false)] + [InlineData(true, 0.3, 0.4, true, 0.3, true)] + [InlineData(true, 0.3, null, true, 0.3, true)] + [InlineData(true, null, 0.4, true, 0.4, false)] + [InlineData(true, null, null, true, 0.0, false)] + [InlineData(false, 0.3, 0.4, true, 0.3, true)] + [InlineData(false, 0.3, null, true, 0.3, true)] + [InlineData(false, null, 0.4, false, 0.4, false)] + [InlineData(false, null, null, false, 0.0, false)] + public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSampleRate(bool? isSampled, double? tracesSampler, double? tracesSampleRate, bool expectedIsSampled, double expectedSampleRate, bool expectedDscOverwritten) { #if DEBUG Skip.If(isSampled is false && expectedIsSampled, $"Forced sampling via {nameof(ITransactionContext.IsSampled)} is not considered when {nameof(SentryOptions.TracesSampler)} is used, resulting in `Debug.Assert` of {nameof(TransactionTracer)} `.ctor`."); @@ -756,15 +756,29 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSamp { var transactionTracer = transaction.Should().BeOfType().Subject; transactionTracer.SampleRate.Should().Be(expectedSampleRate); - transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); - transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + if (expectedDscOverwritten) + { + transactionTracer.DynamicSamplingContext.Should().NotBeSameAs(dsc); + transactionTracer.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + } + else + { + transactionTracer.DynamicSamplingContext.Should().BeSameAs(dsc); + } } else { var unsampledTransaction = transaction.Should().BeOfType().Subject; unsampledTransaction.SampleRate.Should().Be(expectedSampleRate); - unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); - unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + if (expectedDscOverwritten) + { + unsampledTransaction.DynamicSamplingContext.Should().NotBeSameAs(dsc); + unsampledTransaction.DynamicSamplingContext.Should().BeEquivalentTo(dsc.ReplaceSampleRate(expectedSampleRate)); + } + else + { + unsampledTransaction.DynamicSamplingContext.Should().BeSameAs(dsc); + } } } From 23829431d069498ceaa2d4c1432d156273e2264e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:14:24 +0200 Subject: [PATCH 14/17] ref: remove Debug.Assert --- src/Sentry/Internal/Hub.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index f5f7e7a7c8..629e96128a 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -197,9 +197,8 @@ internal ITransactionTracer StartTransaction( // If the sampling decision isn't made by a trace sampler we check the trace header first (from the context) or // finally fallback to Random sampling if the decision has been made by no other means - if (sampleRate == null) + if (isSampled == null) { - Debug.Assert(isSampled == null); sampleRate = _options.TracesSampleRate ?? 0.0; isSampled = context.IsSampled ?? SampleRandHelper.IsSampled(sampleRand, sampleRate.Value); From 8c5588f26d5b56e2e000da636fb751ecc5441e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:35:56 +0200 Subject: [PATCH 15/17] docs: add comment to test --- test/Sentry.Tests/HubTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 6c3b78ddd8..e5a24393e7 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -714,6 +714,11 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate transactionTracer.DynamicSamplingContext.Should().BeSameAs(dsc); } + // overwrite the 'sample_rate' of the Dynamic Sampling Context (DSC) when a sampling decisions is made in the downstream SDK + // 1. overwrite when 'TracesSampler' reaches a sampling decision + // 2. keep when a sampling decision has been made upstream (via 'TransactionContext.IsSampled') + // 3. overwrite when 'TracesSampleRate' reaches a sampling decision + // 4. keep otherwise [SkippableTheory] [InlineData(null, 0.3, 0.4, true, 0.3, true)] [InlineData(null, 0.3, null, true, 0.3, true)] From 758028ccebba3d3037c6c43ffd65b0270a0cca94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:30:28 +0200 Subject: [PATCH 16/17] docs: update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01189e8d3d..11f2237078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Update `sample_rate` of _Dynamic Sampling Context (DSC)_ when making sampling decisions ([#4374](https://github.com/getsentry/sentry-dotnet/pull/4374)) + ## 5.13.0 ### Features @@ -8,7 +14,6 @@ ### Fixes -- Overwrite `sample_rate` of _Dynamic Sampling Context (DSC)_ when making sampling decisions ([#4374](https://github.com/getsentry/sentry-dotnet/pull/4374)) - Source context for class libraries when running on Android in Release mode ([#4294](https://github.com/getsentry/sentry-dotnet/pull/4294)) - Native AOT: don't load SentryNative on unsupported platforms ([#4347](https://github.com/getsentry/sentry-dotnet/pull/4347)) - Fixed issue introduced in release 5.12.0 that might prevent other middleware or user code from reading request bodies ([#4373](https://github.com/getsentry/sentry-dotnet/pull/4373)) From a13b7e0437ebf51775d6fb39204384315c23ad14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Wed, 30 Jul 2025 11:00:51 +0200 Subject: [PATCH 17/17] fix: TransactionTracer was assumed to be considered even when TracesSampler applied --- src/Sentry/TransactionTracer.cs | 1 - test/Sentry.Tests/HubTests.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index 5966a224da..767ccf977c 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -226,7 +226,6 @@ internal TransactionTracer(IHub hub, string name, string operation, TransactionN /// internal TransactionTracer(IHub hub, ITransactionContext context, TimeSpan? idleTimeout = null) { - Debug.Assert(context.IsSampled ?? true, "context.IsSampled should always be true when creating a TransactionTracer"); _hub = hub; _options = _hub.GetSentryOptions(); Name = context.Name; diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index e5a24393e7..91db9f4956 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -734,10 +734,6 @@ public void StartTransaction_DynamicSamplingContextWithSampleRate_UsesSampleRate [InlineData(false, null, null, false, 0.0, false)] public void StartTransaction_DynamicSamplingContextWithSampleRate_OverwritesSampleRate(bool? isSampled, double? tracesSampler, double? tracesSampleRate, bool expectedIsSampled, double expectedSampleRate, bool expectedDscOverwritten) { -#if DEBUG - Skip.If(isSampled is false && expectedIsSampled, $"Forced sampling via {nameof(ITransactionContext.IsSampled)} is not considered when {nameof(SentryOptions.TracesSampler)} is used, resulting in `Debug.Assert` of {nameof(TransactionTracer)} `.ctor`."); -#endif - // Arrange var transactionContext = new TransactionContext("name", "operation", isSampled: isSampled); var dsc = BaggageHeader.Create(new List>