diff --git a/CHANGELOG.md b/CHANGELOG.md
index 156cc1d0c4..508cd01edb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,17 +2,18 @@
## Unreleased
+### Features
+
+- New source generator allows Sentry to see true build variables like PublishAot and PublishTrimmed to properly adapt checks in the Sentry SDK ([#4101](https://github.com/getsentry/sentry-dotnet/pull/4101))
+- Auto breadcrumbs now include all .NET MAUI gesture recognizer events ([#4124](https://github.com/getsentry/sentry-dotnet/pull/4124))
+- Associate replays with errors and traces on Android ([#4133](https://github.com/getsentry/sentry-dotnet/pull/4133))
+
### Fixes
- Redact Authorization headers before sending events to Sentry ([#4164](https://github.com/getsentry/sentry-dotnet/pull/4164))
- Remove Strong Naming from Sentry.Hangfire ([#4099](https://github.com/getsentry/sentry-dotnet/pull/4099))
- Increase `RequestSize.Small` threshold from 1 kB to 4 kB to match other SDKs ([#4177](https://github.com/getsentry/sentry-dotnet/pull/4177))
-### Features
-
-- New source generator allows Sentry to see true build variables like PublishAot and PublishTrimmed to properly adapt checks in the Sentry SDK ([#4101](https://github.com/getsentry/sentry-dotnet/pull/4101))
-- Auto breadcrumbs now include all .NET MAUI gesture recognizer events ([#4124](https://github.com/getsentry/sentry-dotnet/pull/4124))
-
### Dependencies
- Bump CLI from v2.43.1 to v2.45.0 ([#4169](https://github.com/getsentry/sentry-dotnet/pull/4169), [#4179](https://github.com/getsentry/sentry-dotnet/pull/4179))
@@ -23,7 +24,7 @@
### Features
-- When setting a transaction on the scope, the SDK will attempt to sync the transaction's trace context with the SDK on the native layer. Finishing a transaction will now also start a new trace ([#4153](https://github.com/getsentry/sentry-dotnet/pull/4153))
+- When setting a transaction on the scope, the SDK will attempt to sync the transaction's trace context with the SDK on the native layer. Finishing a transaction will now also start a new trace ([#4153](https://github.com/getsentry/sentry-dotnet/pull/4153))
- Added `CaptureFeedback` overload with `configureScope` parameter ([#4073](https://github.com/getsentry/sentry-dotnet/pull/4073))
- Custom SessionReplay masks in MAUI Android apps ([#4121](https://github.com/getsentry/sentry-dotnet/pull/4121))
@@ -45,7 +46,7 @@
### Features
- Option to disable the SentryNative integration ([#4107](https://github.com/getsentry/sentry-dotnet/pull/4107), [#4134](https://github.com/getsentry/sentry-dotnet/pull/4134))
- - To disable it, add this msbuild property: `false`
+ - To disable it, add this msbuild property: `false`
- Reintroduced experimental support for Session Replay on Android ([#4097](https://github.com/getsentry/sentry-dotnet/pull/4097))
- If an incoming HTTP request has the `traceparent` header, it is now parsed and interpreted like the `sentry-trace` header. Outgoing requests now contain the `traceparent` header to facilitate integration with servesr that only support the [W3C Trace Context](https://www.w3.org/TR/trace-context/). ([#4084](https://github.com/getsentry/sentry-dotnet/pull/4084))
diff --git a/src/Sentry.AspNet/HttpContextExtensions.cs b/src/Sentry.AspNet/HttpContextExtensions.cs
index 220413140f..b652062969 100644
--- a/src/Sentry.AspNet/HttpContextExtensions.cs
+++ b/src/Sentry.AspNet/HttpContextExtensions.cs
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
+using Sentry.Internal;
using Sentry.Protocol;
namespace Sentry.AspNet;
@@ -125,7 +126,7 @@ public static ITransactionTracer StartSentryTransaction(this HttpContext httpCon
["__HttpContext"] = httpContext,
};
- // Set the Dynamic Sampling Context from the baggage header, if it exists.
+ // Set the Dynamic Sampling Context from the baggage header, if it exists
var dynamicSamplingContext = baggageHeader?.CreateDynamicSamplingContext();
if (traceHeader is not null && baggageHeader is null)
diff --git a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs
index 60c43b2587..d878e1c00a 100644
--- a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs
+++ b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs
@@ -2,6 +2,7 @@
using Microsoft.Extensions.Options;
using Sentry.AspNetCore.Extensions;
using Sentry.Extensibility;
+using Sentry.Internal;
using Sentry.Internal.OpenTelemetry;
namespace Sentry.AspNetCore;
@@ -64,7 +65,6 @@ public SentryTracingMiddleware(
? traceHeaderObject as SentryTraceHeader : null;
var baggageHeader = context.Items.TryGetValue(SentryMiddleware.BaggageHeaderItemKey, out var baggageHeaderObject)
? baggageHeaderObject as BaggageHeader : null;
-
var dynamicSamplingContext = baggageHeader?.CreateDynamicSamplingContext();
if (traceHeader is not null && baggageHeader is null)
diff --git a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
index 39230143a8..b7942bb21b 100644
--- a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
+++ b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
@@ -13,6 +13,7 @@ public class SentrySpanProcessor : BaseProcessor
{
private readonly IHub _hub;
internal readonly IEnumerable _enrichers;
+ private readonly IReplaySession _replaySession;
internal const string OpenTelemetryOrigin = "auto.otel";
// ReSharper disable once MemberCanBePrivate.Global - Used by tests
@@ -38,7 +39,7 @@ public SentrySpanProcessor(IHub hub) : this(hub, null)
{
}
- internal SentrySpanProcessor(IHub hub, IEnumerable? enrichers)
+ internal SentrySpanProcessor(IHub hub, IEnumerable? enrichers, IReplaySession? replaySession = null)
{
_hub = hub;
_realHub = new Lazy(() =>
@@ -57,7 +58,8 @@ internal SentrySpanProcessor(IHub hub, IEnumerable? enri
"You should use the TracerProviderBuilderExtensions to configure Sentry with OpenTelemetry");
}
- _enrichers = enrichers ?? Enumerable.Empty();
+ _enrichers = enrichers ?? [];
+ _replaySession = replaySession ?? ReplaySession.Instance;
_options = hub.GetSentryOptions();
if (_options is null)
@@ -158,7 +160,7 @@ private void CreateRootSpan(Activity data)
};
var baggageHeader = data.Baggage.AsBaggageHeader();
- var dynamicSamplingContext = baggageHeader.CreateDynamicSamplingContext();
+ var dynamicSamplingContext = baggageHeader.CreateDynamicSamplingContext(_replaySession);
var transaction = (TransactionTracer)_hub.StartTransaction(
transactionContext, new Dictionary(), dynamicSamplingContext
);
diff --git a/src/Sentry/DynamicSamplingContext.cs b/src/Sentry/DynamicSamplingContext.cs
index e85ec88b84..cda413c7bc 100644
--- a/src/Sentry/DynamicSamplingContext.cs
+++ b/src/Sentry/DynamicSamplingContext.cs
@@ -20,15 +20,15 @@ internal class DynamicSamplingContext
///
public static readonly DynamicSamplingContext Empty = new(new Dictionary().AsReadOnly());
- private DynamicSamplingContext(
- SentryId traceId,
+ private DynamicSamplingContext(SentryId traceId,
string publicKey,
bool? sampled,
double? sampleRate = null,
double? sampleRand = null,
string? release = null,
string? environment = null,
- string? transactionName = null)
+ string? transactionName = null,
+ IReplaySession? replaySession = null)
{
// Validate and set required values
if (traceId == SentryId.Empty)
@@ -51,7 +51,7 @@ private DynamicSamplingContext(
throw new ArgumentOutOfRangeException(nameof(sampleRand), "Arg invalid if < 0.0 or >= 1.0");
}
- var items = new Dictionary(capacity: 8)
+ var items = new Dictionary(capacity: 9)
{
["trace_id"] = traceId.ToString(),
["public_key"] = publicKey,
@@ -88,12 +88,29 @@ private DynamicSamplingContext(
items.Add("transaction", transactionName);
}
+ if (replaySession?.ActiveReplayId is { } replayId && replayId != SentryId.Empty)
+ {
+ items.Add("replay_id", replayId.ToString());
+ }
+
Items = items;
}
public BaggageHeader ToBaggageHeader() => BaggageHeader.Create(Items, useSentryPrefix: true);
- public static DynamicSamplingContext? CreateFromBaggageHeader(BaggageHeader baggage)
+ public DynamicSamplingContext WithReplayId(IReplaySession? replaySession)
+ {
+ if (replaySession?.ActiveReplayId is not { } replayId || replayId == SentryId.Empty)
+ {
+ return this;
+ }
+
+ var items = Items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
+ items["replay_id"] = replayId.ToString();
+ return new DynamicSamplingContext(items);
+ }
+
+ public static DynamicSamplingContext? CreateFromBaggageHeader(BaggageHeader baggage, IReplaySession? replaySession)
{
var items = baggage.GetSentryMembers();
@@ -144,10 +161,19 @@ private DynamicSamplingContext(
}
items.Add("sample_rand", rand.ToString("N4", CultureInfo.InvariantCulture));
}
+
+ if (replaySession?.ActiveReplayId is { } replayId)
+ {
+ // Any upstream replay_id will be propagated only if the current process hasn't started it's own replay session.
+ // Otherwise we have to overwrite this as it's the only way to communicate the replayId to Sentry Relay.
+ // In Mobile apps this should never be a problem.
+ items["replay_id"] = replayId.ToString();
+ }
+
return new DynamicSamplingContext(items);
}
- public static DynamicSamplingContext CreateFromTransaction(TransactionTracer transaction, SentryOptions options)
+ public static DynamicSamplingContext CreateFromTransaction(TransactionTracer transaction, SentryOptions options, IReplaySession? replaySession)
{
// These should already be set on the transaction.
var publicKey = options.ParsedDsn.PublicKey;
@@ -161,18 +187,18 @@ public static DynamicSamplingContext CreateFromTransaction(TransactionTracer tra
var release = options.SettingLocator.GetRelease();
var environment = options.SettingLocator.GetEnvironment();
- return new DynamicSamplingContext(
- traceId,
+ return new DynamicSamplingContext(traceId,
publicKey,
sampled,
sampleRate,
sampleRand,
release,
environment,
- transactionName);
+ transactionName,
+ replaySession);
}
- public static DynamicSamplingContext CreateFromPropagationContext(SentryPropagationContext propagationContext, SentryOptions options)
+ public static DynamicSamplingContext CreateFromPropagationContext(SentryPropagationContext propagationContext, SentryOptions options, IReplaySession? replaySession)
{
var traceId = propagationContext.TraceId;
var publicKey = options.ParsedDsn.PublicKey;
@@ -184,18 +210,20 @@ public static DynamicSamplingContext CreateFromPropagationContext(SentryPropagat
publicKey,
null,
release: release,
- environment: environment);
+ environment: environment,
+ replaySession: replaySession
+ );
}
}
internal static class DynamicSamplingContextExtensions
{
- public static DynamicSamplingContext? CreateDynamicSamplingContext(this BaggageHeader baggage)
- => DynamicSamplingContext.CreateFromBaggageHeader(baggage);
+ public static DynamicSamplingContext? CreateDynamicSamplingContext(this BaggageHeader baggage, IReplaySession? replaySession = null)
+ => DynamicSamplingContext.CreateFromBaggageHeader(baggage, replaySession);
- public static DynamicSamplingContext CreateDynamicSamplingContext(this TransactionTracer transaction, SentryOptions options)
- => DynamicSamplingContext.CreateFromTransaction(transaction, options);
+ public static DynamicSamplingContext CreateDynamicSamplingContext(this TransactionTracer transaction, SentryOptions options, IReplaySession? replaySession)
+ => DynamicSamplingContext.CreateFromTransaction(transaction, options, replaySession);
- public static DynamicSamplingContext CreateDynamicSamplingContext(this SentryPropagationContext propagationContext, SentryOptions options)
- => DynamicSamplingContext.CreateFromPropagationContext(propagationContext, options);
+ public static DynamicSamplingContext CreateDynamicSamplingContext(this SentryPropagationContext propagationContext, SentryOptions options, IReplaySession? replaySession)
+ => DynamicSamplingContext.CreateFromPropagationContext(propagationContext, options, replaySession);
}
diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs
index cc9cbd3ef3..ce896d2600 100644
--- a/src/Sentry/Internal/Hub.cs
+++ b/src/Sentry/Internal/Hub.cs
@@ -13,6 +13,7 @@ internal class Hub : IHub, IDisposable
private readonly ISessionManager _sessionManager;
private readonly SentryOptions _options;
private readonly RandomValuesFactory _randomValuesFactory;
+ private readonly IReplaySession _replaySession;
#if MEMORY_DUMP_SUPPORTED
private readonly MemoryMonitor? _memoryMonitor;
@@ -39,7 +40,8 @@ internal Hub(
ISessionManager? sessionManager = null,
ISystemClock? clock = null,
IInternalScopeManager? scopeManager = null,
- RandomValuesFactory? randomValuesFactory = null)
+ RandomValuesFactory? randomValuesFactory = null,
+ IReplaySession? replaySession = null)
{
if (string.IsNullOrWhiteSpace(options.Dsn))
{
@@ -55,7 +57,7 @@ internal Hub(
_sessionManager = sessionManager ?? new GlobalSessionManager(options);
_clock = clock ?? SystemClock.Clock;
client ??= new SentryClient(options, randomValuesFactory: _randomValuesFactory, sessionManager: _sessionManager);
-
+ _replaySession = replaySession ?? ReplaySession.Instance;
ScopeManager = scopeManager ?? new SentryScopeManager(options, client);
if (!options.IsGlobalModeEnabled)
@@ -178,10 +180,10 @@ _options.TransactionProfilerFactory is { } profilerFactory &&
}
}
- // Use the provided DSC, or create one based on this transaction.
+ // Use the provided DSC (adding the active replayId if necessary), or create one based on this transaction.
// DSC creation must be done AFTER the sampling decision has been made.
- transaction.DynamicSamplingContext =
- dynamicSamplingContext ?? transaction.CreateDynamicSamplingContext(_options);
+ transaction.DynamicSamplingContext = dynamicSamplingContext?.WithReplayId(_replaySession)
+ ?? transaction.CreateDynamicSamplingContext(_options, _replaySession);
// A sampled out transaction still appears fully functional to the user
// but will be dropped by the client and won't reach Sentry's servers.
@@ -224,7 +226,7 @@ public BaggageHeader GetBaggage()
}
var propagationContext = CurrentScope.PropagationContext;
- return propagationContext.GetOrCreateDynamicSamplingContext(_options).ToBaggageHeader();
+ return propagationContext.GetOrCreateDynamicSamplingContext(_options, _replaySession).ToBaggageHeader();
}
public TransactionContext ContinueTrace(
@@ -254,7 +256,7 @@ public TransactionContext ContinueTrace(
string? name = null,
string? operation = null)
{
- var propagationContext = SentryPropagationContext.CreateFromHeaders(_options.DiagnosticLogger, traceHeader, baggageHeader);
+ var propagationContext = SentryPropagationContext.CreateFromHeaders(_options.DiagnosticLogger, traceHeader, baggageHeader, _replaySession);
ConfigureScope(scope => scope.SetPropagationContext(propagationContext));
return new TransactionContext(
@@ -382,7 +384,7 @@ private void ApplyTraceContextToEvent(SentryEvent evt, SentryPropagationContext
evt.Contexts.Trace.TraceId = propagationContext.TraceId;
evt.Contexts.Trace.SpanId = propagationContext.SpanId;
evt.Contexts.Trace.ParentSpanId = propagationContext.ParentSpanId;
- evt.DynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(_options);
+ evt.DynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(_options, _replaySession);
}
public bool CaptureEnvelope(Envelope envelope) => CurrentClient.CaptureEnvelope(envelope);
@@ -473,10 +475,7 @@ private SentryId CaptureEvent(SentryEvent evt, SentryHint? hint, Scope scope)
var span = GetLinkedSpan(evt) ?? scope.Span;
if (span is not null)
{
- if (span.IsSampled is not false)
- {
- ApplyTraceContextToEvent(evt, span);
- }
+ ApplyTraceContextToEvent(evt, span);
}
else
{
diff --git a/src/Sentry/Internal/ReplaySession.cs b/src/Sentry/Internal/ReplaySession.cs
new file mode 100644
index 0000000000..30f151c894
--- /dev/null
+++ b/src/Sentry/Internal/ReplaySession.cs
@@ -0,0 +1,33 @@
+#if __ANDROID__
+using Sentry.Android.Extensions;
+#endif
+
+namespace Sentry.Internal;
+
+internal interface IReplaySession
+{
+ public SentryId? ActiveReplayId { get; }
+}
+
+internal class ReplaySession : IReplaySession
+{
+ public static readonly IReplaySession Instance = new ReplaySession();
+
+ private ReplaySession()
+ {
+ }
+
+ public SentryId? ActiveReplayId
+ {
+ get
+ {
+#if __ANDROID__
+ // Check to see if a Replay ID is available
+ var replayId = JavaSdk.ScopesAdapter.Instance?.Options?.ReplayController?.ReplayId?.ToSentryId();
+ return (replayId is { } id && id != SentryId.Empty) ? id : null;
+#else
+ return null;
+#endif
+ }
+ }
+}
diff --git a/src/Sentry/SentryPropagationContext.cs b/src/Sentry/SentryPropagationContext.cs
index 19e9bd30d3..24f9550638 100644
--- a/src/Sentry/SentryPropagationContext.cs
+++ b/src/Sentry/SentryPropagationContext.cs
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
+using Sentry.Internal;
namespace Sentry;
@@ -10,12 +11,12 @@ internal class SentryPropagationContext
internal DynamicSamplingContext? _dynamicSamplingContext;
- public DynamicSamplingContext GetOrCreateDynamicSamplingContext(SentryOptions options)
+ public DynamicSamplingContext GetOrCreateDynamicSamplingContext(SentryOptions options, IReplaySession replaySession)
{
if (_dynamicSamplingContext is null)
{
options.LogDebug("Creating the Dynamic Sampling Context from the Propagation Context");
- _dynamicSamplingContext = this.CreateDynamicSamplingContext(options);
+ _dynamicSamplingContext = this.CreateDynamicSamplingContext(options, replaySession);
}
return _dynamicSamplingContext;
@@ -47,7 +48,7 @@ public SentryPropagationContext(SentryPropagationContext? other)
_dynamicSamplingContext = other?._dynamicSamplingContext;
}
- public static SentryPropagationContext CreateFromHeaders(IDiagnosticLogger? logger, SentryTraceHeader? traceHeader, BaggageHeader? baggageHeader)
+ public static SentryPropagationContext CreateFromHeaders(IDiagnosticLogger? logger, SentryTraceHeader? traceHeader, BaggageHeader? baggageHeader, IReplaySession replaySession)
{
logger?.LogDebug("Creating a propagation context from headers.");
@@ -57,7 +58,7 @@ public static SentryPropagationContext CreateFromHeaders(IDiagnosticLogger? logg
return new SentryPropagationContext();
}
- var dynamicSamplingContext = baggageHeader?.CreateDynamicSamplingContext();
+ var dynamicSamplingContext = baggageHeader?.CreateDynamicSamplingContext(replaySession);
return new SentryPropagationContext(traceHeader.TraceId, traceHeader.SpanId, dynamicSamplingContext);
}
}
diff --git a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
index 1b199c9fc4..67a13d7da0 100644
--- a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
+++ b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
@@ -18,6 +18,8 @@ private class Fixture
public List Enrichers { get; set; } = new();
+ private IReplaySession ReplaySession { get; } = Substitute.For();
+
public Fixture()
{
Options = new SentryOptions
@@ -32,11 +34,11 @@ public Fixture()
public Hub Hub { get; private set; }
- public Hub GetHub() => Hub ??= new Hub(Options, Client, SessionManager, Clock, ScopeManager);
+ private Hub GetHub() => Hub ??= new Hub(Options, Client, SessionManager, Clock, ScopeManager, replaySession: ReplaySession);
public SentrySpanProcessor GetSut(IHub hub = null)
{
- return new SentrySpanProcessor(hub ?? GetHub(), Enrichers);
+ return new SentrySpanProcessor(hub ?? GetHub(), Enrichers, ReplaySession);
}
}
@@ -287,7 +289,6 @@ public void OnStart_WithoutParentSpanId_StartsNewTransaction()
transaction.Description.Should().Be(data.DisplayName);
transaction.Status.Should().BeNull();
transaction.StartTimestamp.Should().Be(data.StartTimeUtc);
- _fixture.ScopeManager.Received(1).ConfigureScope(Arg.Any>());
}
}
diff --git a/test/Sentry.Tests/DynamicSamplingContextTests.cs b/test/Sentry.Tests/DynamicSamplingContextTests.cs
index 374be0c371..a3fdb6a8ff 100644
--- a/test/Sentry.Tests/DynamicSamplingContextTests.cs
+++ b/test/Sentry.Tests/DynamicSamplingContextTests.cs
@@ -1,7 +1,27 @@
+using Sentry.Tests.Internals;
+
namespace Sentry.Tests;
public class DynamicSamplingContextTests
{
+ private class Fixture
+ {
+ public SentryId ActiveReplayId { get; } = SentryId.Create();
+ public IReplaySession InactiveReplaySession { get; }
+ public IReplaySession ActiveReplaySession { get; }
+
+ public Fixture()
+ {
+ ActiveReplaySession = Substitute.For();
+ ActiveReplaySession.ActiveReplayId.Returns(ActiveReplayId);
+
+ InactiveReplaySession = Substitute.For();
+ InactiveReplaySession.ActiveReplayId.Returns((SentryId?)null);
+ }
+ }
+
+ private Fixture _fixture = new();
+
[Fact]
public void EmptyContext()
{
@@ -19,7 +39,7 @@ public void CreateFromBaggage_TraceId_Missing()
{"sentry-sample_rate", "1.0"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -34,7 +54,7 @@ public void CreateFromBaggage_TraceId_EmptyGuid()
{"sentry-sample_rate", "1.0"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -49,7 +69,7 @@ public void CreateFromBaggage_TraceId_Invalid()
{"sentry-sample_rate", "1.0"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -63,7 +83,7 @@ public void CreateFromBaggage_PublicKey_Missing()
{"sentry-sample_rate", "1.0"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -78,7 +98,7 @@ public void CreateFromBaggage_PublicKey_Blank()
{"sentry-sample_rate", "1.0"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -92,7 +112,7 @@ public void CreateFromBaggage_SampleRate_Missing()
{"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -107,7 +127,7 @@ public void CreateFromBaggage_SampleRate_Invalid()
{"sentry-sample_rate", "not-a-number"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -122,7 +142,7 @@ public void CreateFromBaggage_SampleRate_TooLow()
{"sentry-sample_rate", "-0.1"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -137,7 +157,7 @@ public void CreateFromBaggage_SampleRate_TooHigh()
{"sentry-sample_rate", "1.1"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -153,7 +173,7 @@ public void CreateFromBaggage_SampleRand_Invalid()
{"sentry-sample_rand", "not-a-number"},
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -169,7 +189,7 @@ public void CreateFromBaggage_SampleRand_TooLow()
{"sentry-sample_rand", "-0.1"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -185,7 +205,7 @@ public void CreateFromBaggage_SampleRand_TooHigh()
{"sentry-sample_rand", "1.0"} // Must be less than 1
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
@@ -200,7 +220,7 @@ public void CreateFromBaggage_NotSampledNoSampleRand_GeneratesSampleRand()
{"sentry-sample_rate", "0.5"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
using var scope = new AssertionScope();
Assert.NotNull(dsc);
@@ -223,7 +243,7 @@ public void CreateFromBaggage_SampledNoSampleRand_GeneratesConsistentSampleRand(
{"sentry-sampled", sampled},
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
using var scope = new AssertionScope();
Assert.NotNull(dsc);
@@ -252,13 +272,15 @@ public void CreateFromBaggage_Sampled_MalFormed()
{"sentry-sampled", "foo"},
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(_fixture.InactiveReplaySession);
Assert.Null(dsc);
}
- [Fact]
- public void CreateFromBaggage_Valid_Minimum()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void CreateFromBaggage_Valid_Minimum(bool replaySessionIsActive)
{
var baggage = BaggageHeader.Create(new List>
{
@@ -267,18 +289,29 @@ public void CreateFromBaggage_Valid_Minimum()
{"sentry-sample_rate", "1.0"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession);
Assert.NotNull(dsc);
- Assert.Equal(4, dsc.Items.Count);
+ Assert.Equal(replaySessionIsActive ? 5 : 4, dsc.Items.Count);
Assert.Equal("43365712692146d08ee11a729dfbcaca", Assert.Contains("trace_id", dsc.Items));
Assert.Equal("d4d82fc1c2c4032a83f3a29aa3a3aff", Assert.Contains("public_key", dsc.Items));
Assert.Equal("1.0", Assert.Contains("sample_rate", dsc.Items));
Assert.Contains("sample_rand", dsc.Items);
+ if (replaySessionIsActive)
+ {
+ // We add the replay_id automatically when we have an active replay session
+ Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", dsc.Items));
+ }
+ else
+ {
+ Assert.DoesNotContain("replay_id", dsc.Items);
+ }
}
- [Fact]
- public void CreateFromBaggage_Valid_Complete()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void CreateFromBaggage_Valid_Complete(bool replaySessionIsActive)
{
var baggage = BaggageHeader.Create(new List>
{
@@ -290,10 +323,11 @@ public void CreateFromBaggage_Valid_Complete()
{"sentry-release", "test@1.0.0+abc"},
{"sentry-environment", "production"},
{"sentry-user_segment", "Group B"},
- {"sentry-transaction", "GET /person/{id}"}
+ {"sentry-transaction", "GET /person/{id}"},
+ {"sentry-replay_id","bfd31b89a59d41c99d96dc2baf840ecd"}
});
- var dsc = baggage.CreateDynamicSamplingContext();
+ var dsc = baggage.CreateDynamicSamplingContext(replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession);
Assert.NotNull(dsc);
Assert.Equal(baggage.Members.Count, dsc.Items.Count);
@@ -306,6 +340,16 @@ public void CreateFromBaggage_Valid_Complete()
Assert.Equal("production", Assert.Contains("environment", dsc.Items));
Assert.Equal("Group B", Assert.Contains("user_segment", dsc.Items));
Assert.Equal("GET /person/{id}", Assert.Contains("transaction", dsc.Items));
+ if (replaySessionIsActive)
+ {
+ // We overwrite the replay_id when we have an active replay session
+ Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", dsc.Items));
+ }
+ else
+ {
+ // If we don't have any active replay session of our own then we propagate whatever was in the baggage header
+ Assert.Equal("bfd31b89a59d41c99d96dc2baf840ecd", Assert.Contains("replay_id", dsc.Items));
+ }
}
[Fact]
@@ -320,10 +364,11 @@ public void ToBaggageHeader()
{"sentry-release", "test@1.0.0+abc"},
{"sentry-environment", "production"},
{"sentry-user_segment", "Group B"},
- {"sentry-transaction", "GET /person/{id}"}
+ {"sentry-transaction", "GET /person/{id}"},
+ {"sentry-replay_id", _fixture.ActiveReplayId.ToString()}
});
- var dsc = original.CreateDynamicSamplingContext();
+ var dsc = original.CreateDynamicSamplingContext(_fixture.ActiveReplaySession);
var result = dsc?.ToBaggageHeader();
@@ -362,10 +407,10 @@ public void CreateFromTransaction(bool? isSampled)
},
};
- var dsc = transaction.CreateDynamicSamplingContext(options);
+ var dsc = transaction.CreateDynamicSamplingContext(options, _fixture.ActiveReplaySession);
Assert.NotNull(dsc);
- Assert.Equal(isSampled.HasValue ? 8 : 7, dsc.Items.Count);
+ Assert.Equal(isSampled.HasValue ? 9 : 8, dsc.Items.Count);
Assert.Equal(traceId.ToString(), Assert.Contains("trace_id", dsc.Items));
Assert.Equal("d4d82fc1c2c4032a83f3a29aa3a3aff", Assert.Contains("public_key", dsc.Items));
if (transaction.IsSampled is { } sampled)
@@ -381,21 +426,34 @@ public void CreateFromTransaction(bool? isSampled)
Assert.Equal("foo@2.4.5", Assert.Contains("release", dsc.Items));
Assert.Equal("staging", Assert.Contains("environment", dsc.Items));
Assert.Equal("GET /person/{id}", Assert.Contains("transaction", dsc.Items));
+ // We add the replay_id automatically when we have an active replay session
+ Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", dsc.Items));
}
- [Fact]
- public void CreateFromPropagationContext_Valid_Complete()
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CreateFromPropagationContext_Valid_Complete(bool replaySessionIsActive)
{
var options = new SentryOptions { Dsn = "https://a@sentry.io/1", Release = "test-release", Environment = "test-environment" };
var propagationContext = new SentryPropagationContext(
SentryId.Parse("43365712692146d08ee11a729dfbcaca"), SpanId.Parse("1234"));
- var dsc = propagationContext.CreateDynamicSamplingContext(options);
+ var dsc = propagationContext.CreateDynamicSamplingContext(options, replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession);
Assert.NotNull(dsc);
Assert.Equal("43365712692146d08ee11a729dfbcaca", Assert.Contains("trace_id", dsc.Items));
Assert.Equal("a", Assert.Contains("public_key", dsc.Items));
Assert.Equal("test-release", Assert.Contains("release", dsc.Items));
Assert.Equal("test-environment", Assert.Contains("environment", dsc.Items));
+ if (replaySessionIsActive)
+ {
+ // We add the replay_id automatically when we have an active replay session
+ Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", dsc.Items));
+ }
+ else
+ {
+ Assert.DoesNotContain("replay_id", dsc.Items);
+ }
}
}
diff --git a/test/Sentry.Tests/EventProcessorTests.verify.cs b/test/Sentry.Tests/EventProcessorTests.verify.cs
index ea324e8283..6dd6394fd5 100644
--- a/test/Sentry.Tests/EventProcessorTests.verify.cs
+++ b/test/Sentry.Tests/EventProcessorTests.verify.cs
@@ -1,3 +1,5 @@
+using Sentry.Tests.Internals;
+
namespace Sentry.Tests;
public class EventProcessorTests
diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs
index cc9c510a57..efbca9830b 100644
--- a/test/Sentry.Tests/HubTests.cs
+++ b/test/Sentry.Tests/HubTests.cs
@@ -1,5 +1,6 @@
using System.IO.Abstractions.TestingHelpers;
using Sentry.Internal.Http;
+using Sentry.Tests.Internals;
namespace Sentry.Tests;
@@ -10,14 +11,11 @@ public partial class HubTests
private class Fixture
{
public SentryOptions Options { get; }
-
public ISentryClient Client { get; set; }
-
public ISessionManager SessionManager { get; set; }
-
public IInternalScopeManager ScopeManager { get; set; }
-
public ISystemClock Clock { get; set; }
+ public IReplaySession ReplaySession { get; }
public Fixture()
{
@@ -29,9 +27,11 @@ public Fixture()
};
Client = Substitute.For();
+
+ ReplaySession = Substitute.For();
}
- public Hub GetSut() => new(Options, Client, SessionManager, Clock, ScopeManager);
+ public Hub GetSut() => new(Options, Client, SessionManager, Clock, ScopeManager, replaySession: ReplaySession);
}
private readonly Fixture _fixture = new();
@@ -173,7 +173,7 @@ public void CaptureException_TransactionFinished_Gets_DSC_From_LinkedSpan()
{"sentry-trace_id", "75302ac48a024bde9a3b3734a82e36c8"},
{"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"},
{"sentry-replay_id","bfd31b89a59d41c99d96dc2baf840ecd"}
- }).CreateDynamicSamplingContext();
+ }).CreateDynamicSamplingContext(_fixture.ReplaySession);
var transaction = hub.StartTransaction(
transactionContext,
@@ -193,7 +193,7 @@ public void CaptureException_TransactionFinished_Gets_DSC_From_LinkedSpan()
}
[Fact]
- public void CaptureException_ActiveSpanExistsOnScopeButIsSampledOut_EventIsNotLinkedToSpan()
+ public void CaptureException_ActiveSpanExistsOnScopeButIsSampledOut_EventIsLinkedToSpan()
{
// Arrange
_fixture.Options.TracesSampleRate = 0.0;
@@ -209,8 +209,8 @@ public void CaptureException_ActiveSpanExistsOnScopeButIsSampledOut_EventIsNotLi
// Assert
_fixture.Client.Received(1).CaptureEvent(
Arg.Is(evt =>
- evt.Contexts.Trace.TraceId == default &&
- evt.Contexts.Trace.SpanId == default),
+ evt.Contexts.Trace.TraceId == transaction.TraceId &&
+ evt.Contexts.Trace.SpanId == transaction.SpanId),
Arg.Any(), Arg.Any());
}
@@ -680,6 +680,82 @@ public void StartTransaction_SameInstrumenter_SampledIn()
transaction.IsSampled.Should().BeTrue();
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void StartTransaction_DynamicSamplingContextWithReplayId_UsesActiveReplaySessionId(bool replaySessionIsActive)
+ {
+ // Arrange
+ var transactionContext = new TransactionContext("name", "operation");
+
+ var dummyReplaySession = Substitute.For();
+ dummyReplaySession.ActiveReplayId.Returns((SentryId?)null); // So the replay id in the baggage header is used
+ var dsc = BaggageHeader.Create(new List>
+ {
+ {"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"},
+ {"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"}
+ }).CreateDynamicSamplingContext(dummyReplaySession);
+
+ _fixture.Options.TracesSampleRate = 1.0;
+ _fixture.ReplaySession.ActiveReplayId.Returns(replaySessionIsActive ? SentryId.Create() : null); // This one gets used by the SUT
+ var hub = _fixture.GetSut();
+
+ // Act
+ var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc);
+
+ // Assert
+ var transactionTracer = ((TransactionTracer)transaction);
+ transactionTracer.IsSampled.Should().Be(true);
+ transactionTracer.DynamicSamplingContext.Should().NotBeNull();
+ foreach (var dscItem in dsc!.Items)
+ {
+ 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);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void StartTransaction_NoDynamicSamplingContext_UsesActiveReplaySessionId(bool replaySessionIsActive)
+ {
+ // Arrange
+ var transactionContext = new TransactionContext("name", "operation");
+ _fixture.ReplaySession.ActiveReplayId.Returns(replaySessionIsActive ? SentryId.Create() : null);
+ var hub = _fixture.GetSut();
+
+ // Act
+ var transaction = hub.StartTransaction(transactionContext, new Dictionary());
+
+ // Assert
+ var transactionTracer = ((TransactionTracer)transaction);
+ transactionTracer.SampleRand.Should().NotBeNull();
+ transactionTracer.DynamicSamplingContext.Should().NotBeNull();
+ if (replaySessionIsActive)
+ {
+ // We add the replay_id when we have an active replay session
+ transactionTracer.DynamicSamplingContext!.Items["replay_id"].Should().Be(_fixture.ReplaySession.ActiveReplayId.ToString());
+ }
+ else
+ {
+ transactionTracer.DynamicSamplingContext!.Items.Should().NotContainKey("replay_id");
+ }
+ }
+
[Fact]
public void StartTransaction_NoDynamicSamplingContext_GeneratesSampleRand()
{
@@ -705,12 +781,11 @@ public void StartTransaction_DynamicSamplingContextWithoutSampleRand_SampleRandN
{
// Arrange
var transactionContext = new TransactionContext("name", "operation");
- var customContext = new Dictionary();
var hub = _fixture.GetSut();
// Act
- var transaction = hub.StartTransaction(transactionContext, customContext, DynamicSamplingContext.Empty);
+ var transaction = hub.StartTransaction(transactionContext, new Dictionary(), DynamicSamplingContext.Empty);
// Assert
var transactionTracer = ((TransactionTracer)transaction);
@@ -725,7 +800,7 @@ public void StartTransaction_DynamicSamplingContextWithSampleRand_InheritsSample
{
// Arrange
var transactionContext = new TransactionContext("name", "operation");
- var customContext = new Dictionary();
+ var dummyReplaySession = Substitute.For();
var dsc = BaggageHeader.Create(new List>
{
{"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"},
@@ -733,13 +808,13 @@ public void StartTransaction_DynamicSamplingContextWithSampleRand_InheritsSample
{"sentry-sampled", "true"},
{"sentry-sample_rate", "0.5"}, // Required in the baggage header, but ignored by sampling logic
{"sentry-sample_rand", "0.1234"}
- }).CreateDynamicSamplingContext();
+ }).CreateDynamicSamplingContext(dummyReplaySession);
_fixture.Options.TracesSampleRate = 0.4;
var hub = _fixture.GetSut();
// Act
- var transaction = hub.StartTransaction(transactionContext, customContext, dsc);
+ var transaction = hub.StartTransaction(transactionContext, new Dictionary(), dsc);
// Assert
var transactionTracer = ((TransactionTracer)transaction);
@@ -764,7 +839,7 @@ public void StartTransaction_TraceSampler_UsesSampleRand(double sampleRate, bool
{"sentry-sampled", "true"},
{"sentry-sample_rate", "0.5"},
{"sentry-sample_rand", "0.1234"}
- }).CreateDynamicSamplingContext();
+ }).CreateDynamicSamplingContext(_fixture.ReplaySession);
_fixture.Options.TracesSampler = _ => sampleRate;
var hub = _fixture.GetSut();
@@ -788,13 +863,14 @@ public void StartTransaction_StaticSampler_UsesSampleRand(double sampleRate, boo
// Arrange
var transactionContext = new TransactionContext("name", "operation");
var customContext = new Dictionary();
+ var dummyReplaySession = Substitute.For();
var dsc = BaggageHeader.Create(new List>
{
{"sentry-trace_id", "43365712692146d08ee11a729dfbcaca"},
{"sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff"},
{"sentry-sample_rate", "0.5"}, // Static sampling ignores this and uses options.TracesSampleRate instead
{"sentry-sample_rand", "0.1234"}
- }).CreateDynamicSamplingContext();
+ }).CreateDynamicSamplingContext(dummyReplaySession);
_fixture.Options.TracesSampleRate = sampleRate;
var hub = _fixture.GetSut();
diff --git a/test/Sentry.Tests/Protocol/SentryTransactionTests.cs b/test/Sentry.Tests/Protocol/SentryTransactionTests.cs
index c4f075dd00..7a85611425 100644
--- a/test/Sentry.Tests/Protocol/SentryTransactionTests.cs
+++ b/test/Sentry.Tests/Protocol/SentryTransactionTests.cs
@@ -55,6 +55,33 @@ public async Task NewTransactionTracer_IdleTimeoutProvided_AutomaticallyFinishes
transaction.IsFinished.Should().BeTrue();
}
+ [Fact]
+ public void NewTransactionTracer_PropagationContextHasReplayId_UsesActiveSessionReplayIdInstead()
+ {
+ // Arrange
+ var hub = Substitute.For();
+ var traceHeader = new SentryTraceHeader(SentryId.Create(), SpanId.Create(), null);
+ var replayContext = Substitute.For();
+ var baggageHeader = BaggageHeader.Create(new List>
+ {
+ { "sentry-sample_rate", "1.0" },
+ { "sentry-sample_rand", "0.1234" },
+ { "sentry-trace_id", "75302ac48a024bde9a3b3734a82e36c8" },
+ { "sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff" },
+ { "sentry-replay_id", "bfd31b89a59d41c99d96dc2baf840ecd" }
+ });
+ var propagationContext = SentryPropagationContext.CreateFromHeaders(null, traceHeader, baggageHeader, replayContext);
+ var scope = new Scope(hub.GetSentryOptions(), propagationContext);
+ hub.ConfigureScope(Arg.Do>(callback => callback(scope)));
+ var transactionContext = new TransactionContext("test-name", "test-operation");
+
+ // Act
+ var actualTransaction = new TransactionTracer(hub, transactionContext);
+
+ // Assert
+ Assert.NotEqual(DateTimeOffset.MinValue, actualTransaction.StartTimestamp);
+ }
+
[Fact]
public void Redact_Redacts_Urls()
{
diff --git a/test/Sentry.Tests/SentryPropagationContextTests.cs b/test/Sentry.Tests/SentryPropagationContextTests.cs
index dc658d2ed9..bb90359d0a 100644
--- a/test/Sentry.Tests/SentryPropagationContextTests.cs
+++ b/test/Sentry.Tests/SentryPropagationContextTests.cs
@@ -2,50 +2,100 @@ namespace Sentry.Tests;
public class SentryPropagationContextTests
{
- [Fact]
- public void CopyConstructor_CreatesCopy()
+ private class Fixture
+ {
+ public SentryId ActiveReplayId { get; } = SentryId.Create();
+ public IReplaySession InactiveReplaySession { get; }
+ public IReplaySession ActiveReplaySession { get; }
+
+ public Fixture()
+ {
+ ActiveReplaySession = Substitute.For();
+ ActiveReplaySession.ActiveReplayId.Returns(ActiveReplayId);
+
+ InactiveReplaySession = Substitute.For();
+ InactiveReplaySession.ActiveReplayId.Returns((SentryId?)null);
+ }
+ }
+
+ private readonly Fixture _fixture = new();
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CopyConstructor_CreatesCopyWithReplayId(bool replaySessionIsActive)
{
var original = new SentryPropagationContext();
- original.GetOrCreateDynamicSamplingContext(new SentryOptions { Dsn = ValidDsn });
+ original.GetOrCreateDynamicSamplingContext(new SentryOptions { Dsn = ValidDsn }, _fixture.InactiveReplaySession);
var copy = new SentryPropagationContext(original);
Assert.Equal(original.TraceId, copy.TraceId);
Assert.Equal(original.SpanId, copy.SpanId);
- Assert.Equal(original._dynamicSamplingContext, copy._dynamicSamplingContext);
+ Assert.Equal(original._dynamicSamplingContext!.Items.Count, copy._dynamicSamplingContext!.Items.Count);
+ foreach (var dscItem in original._dynamicSamplingContext!.Items)
+ {
+ if (dscItem.Key == "replay_id")
+ {
+ copy._dynamicSamplingContext!.Items["replay_id"].Should().Be(replaySessionIsActive
+ // We overwrite the replay_id when we have an active replay session
+ ? _fixture.ActiveReplayId.ToString()
+ // Otherwise we propagate whatever was in the baggage header
+ : dscItem.Value);
+ }
+ else
+ {
+ copy._dynamicSamplingContext!.Items.Should()
+ .Contain(kvp => kvp.Key == dscItem.Key && kvp.Value == dscItem.Value);
+ }
+ }
}
- [Fact]
- public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNull_CreatesDynamicSamplingContext()
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNull_CreatesDynamicSamplingContext(bool replaySessionIsActive)
{
var options = new SentryOptions { Dsn = ValidDsn };
var propagationContext = new SentryPropagationContext();
Assert.Null(propagationContext._dynamicSamplingContext); // Sanity check
- _ = propagationContext.GetOrCreateDynamicSamplingContext(options);
+ _ = propagationContext.GetOrCreateDynamicSamplingContext(options, replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession);
Assert.NotNull(propagationContext._dynamicSamplingContext);
+ if (replaySessionIsActive)
+ {
+ // We add the replay_id automatically when we have an active replay session
+ Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", propagationContext._dynamicSamplingContext.Items));
+ }
+ else
+ {
+ Assert.DoesNotContain("replay_id", propagationContext._dynamicSamplingContext.Items);
+ }
}
- [Fact]
- public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNotNull_ReturnsSameDynamicSamplingContext()
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void GetOrCreateDynamicSamplingContext_DynamicSamplingContextIsNotNull_ReturnsSameDynamicSamplingContext(bool replaySessionIsActive)
{
var options = new SentryOptions { Dsn = ValidDsn };
var propagationContext = new SentryPropagationContext();
- var firstDynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(options);
+ var firstDynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(options, replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession);
- var secondDynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(options);
+ var secondDynamicSamplingContext = propagationContext.GetOrCreateDynamicSamplingContext(options, replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession);
Assert.Same(firstDynamicSamplingContext, secondDynamicSamplingContext);
}
[Fact]
- public void CreateFromHeaders_HeadersNull_CreatesPropagationContextWithTraceAndSpanId()
+ public void CreateFromHeaders_HeadersNull_CreatesPropagationContextWithTraceAndSpanAndReplayId()
{
- var propagationContext = SentryPropagationContext.CreateFromHeaders(null, null, null);
+ var propagationContext = SentryPropagationContext.CreateFromHeaders(null, null, null, _fixture.ActiveReplaySession);
Assert.NotEqual(propagationContext.TraceId, SentryId.Empty);
Assert.NotEqual(propagationContext.SpanId, SpanId.Empty);
+ Assert.Null(propagationContext._dynamicSamplingContext);
}
[Fact]
@@ -53,15 +103,16 @@ public void CreateFromHeaders_TraceHeaderNotNull_CreatesPropagationContextFromTr
{
var traceHeader = new SentryTraceHeader(SentryId.Create(), SpanId.Create(), null);
- var propagationContext = SentryPropagationContext.CreateFromHeaders(null, traceHeader, null);
+ var propagationContext = SentryPropagationContext.CreateFromHeaders(null, traceHeader, null, _fixture.ActiveReplaySession);
Assert.Equal(traceHeader.TraceId, propagationContext.TraceId);
Assert.NotEqual(traceHeader.SpanId, propagationContext.SpanId); // Sanity check
Assert.Equal(traceHeader.SpanId, propagationContext.ParentSpanId);
+ Assert.Null(propagationContext._dynamicSamplingContext);
}
[Fact]
- public void CreateFromHeaders_TraceHeaderNullButBaggageExists_CreatesPropagationContextWithoutDynamicSamplingContext()
+ public void CreateFromHeaders_BaggageExistsButTraceHeaderNull_CreatesPropagationContextWithoutDynamicSamplingContext()
{
var baggageHeader = BaggageHeader.Create(new List>
{
@@ -71,14 +122,17 @@ public void CreateFromHeaders_TraceHeaderNullButBaggageExists_CreatesPropagation
{ "sentry-replay_id", "bfd31b89a59d41c99d96dc2baf840ecd" }
});
- var propagationContext = SentryPropagationContext.CreateFromHeaders(null, null, baggageHeader);
+ var propagationContext = SentryPropagationContext.CreateFromHeaders(null, null, baggageHeader, _fixture.InactiveReplaySession);
Assert.Null(propagationContext._dynamicSamplingContext);
}
- [Fact]
- public void CreateFromHeaders_BaggageHeaderNotNull_CreatesPropagationContextWithDynamicSamplingContext()
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void CreateFromHeaders_BaggageHeaderNotNull_CreatesPropagationContextWithDynamicSamplingContext(bool replaySessionIsActive)
{
+ // Arrange
var traceHeader = new SentryTraceHeader(SentryId.Create(), SpanId.Create(), null);
var baggageHeader = BaggageHeader.Create(new List>
{
@@ -88,9 +142,23 @@ public void CreateFromHeaders_BaggageHeaderNotNull_CreatesPropagationContextWith
{ "sentry-public_key", "d4d82fc1c2c4032a83f3a29aa3a3aff" },
{ "sentry-replay_id", "bfd31b89a59d41c99d96dc2baf840ecd" }
});
+ var replaySession = replaySessionIsActive ? _fixture.ActiveReplaySession : _fixture.InactiveReplaySession;
- var propagationContext = SentryPropagationContext.CreateFromHeaders(null, traceHeader, baggageHeader);
+ // Act
+ var propagationContext = SentryPropagationContext.CreateFromHeaders(null, traceHeader, baggageHeader, replaySession);
- Assert.Equal(5, propagationContext.GetOrCreateDynamicSamplingContext(new SentryOptions()).Items.Count);
+ // Assert
+ var dsc = propagationContext.GetOrCreateDynamicSamplingContext(new SentryOptions(), replaySession);
+ Assert.Equal(5, dsc.Items.Count);
+ if (replaySessionIsActive)
+ {
+ // We add the replay_id automatically when we have an active replay session
+ Assert.Equal(_fixture.ActiveReplayId.ToString(), Assert.Contains("replay_id", dsc.Items));
+ }
+ else
+ {
+ // Otherwise we inherit the replay_id from the baggage header
+ Assert.Equal("bfd31b89a59d41c99d96dc2baf840ecd", Assert.Contains("replay_id", dsc.Items));
+ }
}
}