11import type { Hub , Scope , Span , SpanTimeInput , StartSpanOptions , TransactionContext } from '@sentry/types' ;
22
33import { addNonEnumerableProperty , dropUndefinedKeys , logger , tracingContextFromHeaders } from '@sentry/utils' ;
4+ import { getDynamicSamplingContextFromSpan } from '.' ;
45import { getCurrentScope , getIsolationScope , withScope } from '../currentScopes' ;
56
67import { DEBUG_BUILD } from '../debug-build' ;
78import { getCurrentHub } from '../hub' ;
89import { handleCallbackErrors } from '../utils/handleCallbackErrors' ;
910import { hasTracingEnabled } from '../utils/hasTracingEnabled' ;
10- import { spanTimeInputToSeconds , spanToJSON } from '../utils/spanUtils' ;
11+ import { spanIsSampled , spanTimeInputToSeconds , spanToJSON } from '../utils/spanUtils' ;
1112
1213/**
1314 * Wraps a function with a transaction/span and finishes the span after the function is done.
@@ -21,7 +22,7 @@ import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
2122 * and the `span` returned from the callback will be undefined.
2223 */
2324export function startSpan < T > ( context : StartSpanOptions , callback : ( span : Span | undefined ) => T ) : T {
24- const ctx = normalizeContext ( context ) ;
25+ const spanContext = normalizeContext ( context ) ;
2526
2627 return withScope ( context . scope , scope => {
2728 // eslint-disable-next-line deprecation/deprecation
@@ -30,10 +31,14 @@ export function startSpan<T>(context: StartSpanOptions, callback: (span: Span |
3031 const parentSpan = scope . getSpan ( ) ;
3132
3233 const shouldSkipSpan = context . onlyIfParent && ! parentSpan ;
33- const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
34-
35- // eslint-disable-next-line deprecation/deprecation
36- scope . setSpan ( activeSpan ) ;
34+ const activeSpan = shouldSkipSpan
35+ ? undefined
36+ : createChildSpanOrTransaction ( hub , {
37+ parentSpan,
38+ spanContext,
39+ forceTransaction : context . forceTransaction ,
40+ scope,
41+ } ) ;
3742
3843 return handleCallbackErrors (
3944 ( ) => callback ( activeSpan ) ,
@@ -66,7 +71,7 @@ export function startSpanManual<T>(
6671 context : StartSpanOptions ,
6772 callback : ( span : Span | undefined , finish : ( ) => void ) => T ,
6873) : T {
69- const ctx = normalizeContext ( context ) ;
74+ const spanContext = normalizeContext ( context ) ;
7075
7176 return withScope ( context . scope , scope => {
7277 // eslint-disable-next-line deprecation/deprecation
@@ -75,10 +80,14 @@ export function startSpanManual<T>(
7580 const parentSpan = scope . getSpan ( ) ;
7681
7782 const shouldSkipSpan = context . onlyIfParent && ! parentSpan ;
78- const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction ( hub , parentSpan , ctx ) ;
79-
80- // eslint-disable-next-line deprecation/deprecation
81- scope . setSpan ( activeSpan ) ;
83+ const activeSpan = shouldSkipSpan
84+ ? undefined
85+ : createChildSpanOrTransaction ( hub , {
86+ parentSpan,
87+ spanContext,
88+ forceTransaction : context . forceTransaction ,
89+ scope,
90+ } ) ;
8291
8392 function finishAndSetSpan ( ) : void {
8493 activeSpan && activeSpan . end ( ) ;
@@ -114,7 +123,7 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined {
114123 return undefined ;
115124 }
116125
117- const ctx = normalizeContext ( context ) ;
126+ const spanContext = normalizeContext ( context ) ;
118127 // eslint-disable-next-line deprecation/deprecation
119128 const hub = getCurrentHub ( ) ;
120129 const parentSpan = context . scope
@@ -128,41 +137,19 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined {
128137 return undefined ;
129138 }
130139
131- const isolationScope = getIsolationScope ( ) ;
132- const scope = getCurrentScope ( ) ;
133-
134- let span : Span | undefined ;
135-
136- if ( parentSpan ) {
137- // eslint-disable-next-line deprecation/deprecation
138- span = parentSpan . startChild ( ctx ) ;
139- } else {
140- const { traceId, dsc, parentSpanId, sampled } = {
141- ...isolationScope . getPropagationContext ( ) ,
142- ...scope . getPropagationContext ( ) ,
143- } ;
144-
145- // eslint-disable-next-line deprecation/deprecation
146- span = hub . startTransaction ( {
147- traceId,
148- parentSpanId,
149- parentSampled : sampled ,
150- ...ctx ,
151- metadata : {
152- dynamicSamplingContext : dsc ,
153- // eslint-disable-next-line deprecation/deprecation
154- ...ctx . metadata ,
155- } ,
156- } ) ;
157- }
158-
159- if ( parentSpan ) {
160- addChildSpanToSpan ( parentSpan , span ) ;
161- }
140+ const scope = context . scope || getCurrentScope ( ) ;
162141
163- setCapturedScopesOnSpan ( span , scope , isolationScope ) ;
142+ // Even though we don't actually want to make this span active on the current scope,
143+ // we need to make it active on a temporary scope that we use for event processing
144+ // as otherwise, it won't pick the correct span for the event when processing it
145+ const temporaryScope = scope . clone ( ) ;
164146
165- return span ;
147+ return createChildSpanOrTransaction ( hub , {
148+ parentSpan,
149+ spanContext,
150+ forceTransaction : context . forceTransaction ,
151+ scope : temporaryScope ,
152+ } ) ;
166153}
167154
168155/**
@@ -277,20 +264,47 @@ export const continueTrace: ContinueTrace = <V>(
277264
278265function createChildSpanOrTransaction (
279266 hub : Hub ,
280- parentSpan : Span | undefined ,
281- ctx : TransactionContext ,
267+ {
268+ parentSpan,
269+ spanContext,
270+ forceTransaction,
271+ scope,
272+ } : {
273+ parentSpan : Span | undefined ;
274+ spanContext : TransactionContext ;
275+ forceTransaction ?: boolean ;
276+ scope : Scope ;
277+ } ,
282278) : Span | undefined {
283279 if ( ! hasTracingEnabled ( ) ) {
284280 return undefined ;
285281 }
286282
287283 const isolationScope = getIsolationScope ( ) ;
288- const scope = getCurrentScope ( ) ;
289284
290285 let span : Span | undefined ;
291- if ( parentSpan ) {
286+ if ( parentSpan && ! forceTransaction ) {
287+ // eslint-disable-next-line deprecation/deprecation
288+ span = parentSpan . startChild ( spanContext ) ;
289+ addChildSpanToSpan ( parentSpan , span ) ;
290+ } else if ( parentSpan ) {
291+ // If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
292+ const dsc = getDynamicSamplingContextFromSpan ( parentSpan ) ;
293+ const { traceId, spanId : parentSpanId } = parentSpan . spanContext ( ) ;
294+ const sampled = spanIsSampled ( parentSpan ) ;
295+
292296 // eslint-disable-next-line deprecation/deprecation
293- span = parentSpan . startChild ( ctx ) ;
297+ span = hub . startTransaction ( {
298+ traceId,
299+ parentSpanId,
300+ parentSampled : sampled ,
301+ ...spanContext ,
302+ metadata : {
303+ dynamicSamplingContext : dsc ,
304+ // eslint-disable-next-line deprecation/deprecation
305+ ...spanContext . metadata ,
306+ } ,
307+ } ) ;
294308 } else {
295309 const { traceId, dsc, parentSpanId, sampled } = {
296310 ...isolationScope . getPropagationContext ( ) ,
@@ -302,18 +316,20 @@ function createChildSpanOrTransaction(
302316 traceId,
303317 parentSpanId,
304318 parentSampled : sampled ,
305- ...ctx ,
319+ ...spanContext ,
306320 metadata : {
307321 dynamicSamplingContext : dsc ,
308322 // eslint-disable-next-line deprecation/deprecation
309- ...ctx . metadata ,
323+ ...spanContext . metadata ,
310324 } ,
311325 } ) ;
312326 }
313327
314- if ( parentSpan ) {
315- addChildSpanToSpan ( parentSpan , span ) ;
316- }
328+ // We always set this as active span on the scope
329+ // In the case of this being an inactive span, we ensure to pass a detached scope in here in the first place
330+ // But by having this here, we can ensure that the lookup through `getCapturedScopesOnSpan` results in the correct scope & span combo
331+ // eslint-disable-next-line deprecation/deprecation
332+ scope . setSpan ( span ) ;
317333
318334 setCapturedScopesOnSpan ( span , scope , isolationScope ) ;
319335
0 commit comments