@@ -129,61 +129,72 @@ internal ITransactionTracer StartTransaction(
129129 IReadOnlyDictionary < string , object ? > customSamplingContext ,
130130 DynamicSamplingContext ? dynamicSamplingContext )
131131 {
132- var transaction = new TransactionTracer ( this , context )
133- {
134- SampleRand = dynamicSamplingContext ? . Items . TryGetValue ( "sample_rand" , out var sampleRand ) ?? false
135- ? double . Parse ( sampleRand , NumberStyles . Float , CultureInfo . InvariantCulture )
136- : SampleRandHelper . GenerateSampleRand ( context . TraceId . ToString ( ) )
137- } ;
138-
139132 // If the hub is disabled, we will always sample out. In other words, starting a transaction
140133 // after disposing the hub will result in that transaction not being sent to Sentry.
141- // Additionally, we will always sample out if tracing is explicitly disabled.
142- // Do not invoke the TracesSampler, evaluate the TracesSampleRate, and override any sampling decision
143- // that may have been already set (i.e.: from a sentry-trace header).
144134 if ( ! IsEnabled )
145135 {
146- transaction . IsSampled = false ;
147- transaction . SampleRate = 0.0 ;
136+ return NoOpTransaction . Instance ;
148137 }
149- else
150- {
151- // Except when tracing is disabled, TracesSampler runs regardless of whether a decision
152- // has already been made, as it can be used to override it.
153- if ( _options . TracesSampler is { } tracesSampler )
154- {
155- var samplingContext = new TransactionSamplingContext (
156- context ,
157- customSamplingContext ) ;
158138
159- if ( tracesSampler ( samplingContext ) is { } sampleRate )
160- {
161- transaction . IsSampled = SampleRandHelper . IsSampled ( transaction . SampleRand . Value , sampleRate ) ;
162- transaction . SampleRate = sampleRate ;
163- }
164- }
139+ bool ? isSampled = null ;
140+ double ? sampleRate = null ;
141+ var sampleRand = dynamicSamplingContext ? . Items . TryGetValue ( "sample_rand" , out var dscsampleRand ) ?? false
142+ ? double . Parse ( dscsampleRand , NumberStyles . Float , CultureInfo . InvariantCulture )
143+ : SampleRandHelper . GenerateSampleRand ( context . TraceId . ToString ( ) ) ;
165144
166- // Random sampling runs only if the sampling decision hasn't been made already.
167- if ( transaction . IsSampled == null )
145+ // TracesSampler runs regardless of whether a decision has already been made, as it can be used to override it.
146+ if ( _options . TracesSampler is { } tracesSampler )
147+ {
148+ var samplingContext = new TransactionSamplingContext (
149+ context ,
150+ customSamplingContext ) ;
151+
152+ if ( tracesSampler ( samplingContext ) is { } samplerSampleRate )
168153 {
169- var sampleRate = _options . TracesSampleRate ?? 0.0 ;
170- transaction . IsSampled = SampleRandHelper . IsSampled ( transaction . SampleRand . Value , sampleRate ) ;
171- transaction . SampleRate = sampleRate ;
154+ // The TracesSampler trumps all other sampling decisions (even the trace header)
155+ sampleRate = samplerSampleRate ;
156+ isSampled = SampleRandHelper . IsSampled ( sampleRand , sampleRate . Value ) ;
172157 }
158+ }
173159
174- if ( transaction . IsSampled is true &&
175- _options . TransactionProfilerFactory is { } profilerFactory &&
176- _randomValuesFactory . NextBool ( _options . ProfilesSampleRate ?? 0.0 ) )
160+ // If the sampling decision isn't made by a trace sampler we check the trace header first (from the context) or
161+ // finally fallback to Random sampling if the decision has been made by no other means
162+ sampleRate ??= _options . TracesSampleRate ?? 0.0 ;
163+ isSampled ??= context . IsSampled ?? SampleRandHelper . IsSampled ( sampleRand , sampleRate . Value ) ;
164+
165+ // Make sure there is a replayId (if available) on the provided DSC (if any).
166+ dynamicSamplingContext = dynamicSamplingContext ? . WithReplayId ( _replaySession ) ;
167+
168+ if ( isSampled is false )
169+ {
170+ var unsampledTransaction = new UnsampledTransaction ( this , context )
177171 {
178- // TODO cancellation token based on Hub being closed?
179- transaction . TransactionProfiler = profilerFactory . Start ( transaction , CancellationToken . None ) ;
180- }
172+ SampleRate = sampleRate ,
173+ SampleRand = sampleRand ,
174+ DynamicSamplingContext = dynamicSamplingContext // Default to the provided DSC
175+ } ;
176+ // If no DSC was provided, create one based on this transaction.
177+ // Must be done AFTER the sampling decision has been made (the DSC propagates sampling decisions).
178+ unsampledTransaction . DynamicSamplingContext ??= unsampledTransaction . CreateDynamicSamplingContext ( _options , _replaySession ) ;
179+ return unsampledTransaction ;
181180 }
182181
183- // Use the provided DSC (adding the active replayId if necessary), or create one based on this transaction.
184- // DSC creation must be done AFTER the sampling decision has been made.
185- transaction . DynamicSamplingContext = dynamicSamplingContext ? . WithReplayId ( _replaySession )
186- ?? transaction . CreateDynamicSamplingContext ( _options , _replaySession ) ;
182+ var transaction = new TransactionTracer ( this , context )
183+ {
184+ SampleRate = sampleRate ,
185+ SampleRand = sampleRand ,
186+ DynamicSamplingContext = dynamicSamplingContext // Default to the provided DSC
187+ } ;
188+ // If no DSC was provided, create one based on this transaction.
189+ // Must be done AFTER the sampling decision has been made (the DSC propagates sampling decisions).
190+ transaction . DynamicSamplingContext ??= transaction . CreateDynamicSamplingContext ( _options , _replaySession ) ;
191+
192+ if ( _options . TransactionProfilerFactory is { } profilerFactory &&
193+ _randomValuesFactory . NextBool ( _options . ProfilesSampleRate ?? 0.0 ) )
194+ {
195+ // TODO cancellation token based on Hub being closed?
196+ transaction . TransactionProfiler = profilerFactory . Start ( transaction , CancellationToken . None ) ;
197+ }
187198
188199 // A sampled out transaction still appears fully functional to the user
189200 // but will be dropped by the client and won't reach Sentry's servers.
@@ -220,7 +231,7 @@ public SentryTraceHeader GetTraceHeader()
220231 public BaggageHeader GetBaggage ( )
221232 {
222233 var span = GetSpan ( ) ;
223- if ( span ? . GetTransaction ( ) is TransactionTracer { DynamicSamplingContext : { IsEmpty : false } dsc } )
234+ if ( span ? . GetTransaction ( ) . GetDynamicSamplingContext ( ) is { IsEmpty : false } dsc )
224235 {
225236 return dsc . ToBaggageHeader ( ) ;
226237 }
@@ -373,9 +384,9 @@ private void ApplyTraceContextToEvent(SentryEvent evt, ISpan span)
373384 evt . Contexts . Trace . TraceId = span . TraceId ;
374385 evt . Contexts . Trace . ParentSpanId = span . ParentSpanId ;
375386
376- if ( span . GetTransaction ( ) is TransactionTracer transactionTracer )
387+ if ( span . GetTransaction ( ) . GetDynamicSamplingContext ( ) is { } dsc )
377388 {
378- evt . DynamicSamplingContext = transactionTracer . DynamicSamplingContext ;
389+ evt . DynamicSamplingContext = dsc ;
379390 }
380391 }
381392
0 commit comments