@@ -39,7 +39,7 @@ import { sampleSpan } from './sampling';
3939import { SentryNonRecordingSpan , spanIsNonRecordingSpan } from './sentryNonRecordingSpan' ;
4040import { SentrySpan } from './sentrySpan' ;
4141import { SPAN_STATUS_ERROR } from './spanstatus' ;
42- import { setCapturedScopesOnSpan } from './utils' ;
42+ import { getCapturedScopesOnSpan , setCapturedScopesOnSpan } from './utils' ;
4343import type { Client } from '../client' ;
4444import { SUPPRESS_TRACING_KEY } from './constants' ;
4545
@@ -54,11 +54,6 @@ import { SUPPRESS_TRACING_KEY } from './constants';
5454 * it may just be a non-recording span if the span is not sampled or if tracing is disabled.
5555 */
5656export function startSpan < T > ( options : StartSpanOptions , callback : ( span : Span ) => T ) : T {
57- const acs = getAcs ( ) ;
58- if ( acs . startSpan ) {
59- return acs . startSpan ( options , callback ) ;
60- }
61-
6257 const spanArguments = parseSentrySpanArguments ( options ) ;
6358 const { forceTransaction, parentSpan : customParentSpan , scope : customScope } = options ;
6459
@@ -72,7 +67,7 @@ export function startSpan<T>(options: StartSpanOptions, callback: (span: Span) =
7267
7368 return wrapper ( ( ) => {
7469 const scope = getCurrentScope ( ) ;
75- const parentSpan = getParentSpan ( scope , customParentSpan ) ;
70+ const parentSpan = getParentSpan ( customScope ?? scope , customParentSpan ) ;
7671 const client = getClient ( ) ;
7772
7873 const missingRequiredParent = options . onlyIfParent && ! parentSpan ;
@@ -111,11 +106,6 @@ export function startSpan<T>(options: StartSpanOptions, callback: (span: Span) =
111106 * it may just be a non-recording span if the span is not sampled or if tracing is disabled.
112107 */
113108export function startSpanManual < T > ( options : StartSpanOptions , callback : ( span : Span , finish : ( ) => void ) => T ) : T {
114- const acs = getAcs ( ) ;
115- if ( acs . startSpanManual ) {
116- return acs . startSpanManual ( options , callback ) ;
117- }
118-
119109 const spanArguments = parseSentrySpanArguments ( options ) ;
120110 const { forceTransaction, parentSpan : customParentSpan , scope : customScope } = options ;
121111
@@ -127,7 +117,7 @@ export function startSpanManual<T>(options: StartSpanOptions, callback: (span: S
127117
128118 return wrapper ( ( ) => {
129119 const scope = getCurrentScope ( ) ;
130- const parentSpan = getParentSpan ( scope , customParentSpan ) ;
120+ const parentSpan = getParentSpan ( customScope ?? scope , customParentSpan ) ;
131121
132122 const missingRequiredParent = options . onlyIfParent && ! parentSpan ;
133123 const activeSpan = missingRequiredParent
@@ -162,39 +152,20 @@ export function startSpanManual<T>(options: StartSpanOptions, callback: (span: S
162152 * it may just be a non-recording span if the span is not sampled or if tracing is disabled.
163153 */
164154export function startInactiveSpan ( options : StartSpanOptions ) : Span {
165- const acs = getAcs ( ) ;
166- if ( acs . startInactiveSpan ) {
167- return acs . startInactiveSpan ( options ) ;
168- }
169-
170- return _startInactiveSpanImpl ( options ) ;
171- }
172-
173- /**
174- * Internal version of startInactiveSpan that bypasses the ACS check.
175- * Used by SentryTracerProvider to create spans without triggering recursion
176- * through ACS overrides.
177- * @hidden
178- */
179- export function _INTERNAL_startInactiveSpan ( options : StartSpanOptions ) : Span {
180- return _startInactiveSpanImpl ( options ) ;
181- }
182-
183- function _startInactiveSpanImpl ( options : StartSpanOptions ) : Span {
184155 const spanArguments = parseSentrySpanArguments ( options ) ;
185- const { forceTransaction, parentSpan : customParentSpan } = options ;
156+ const { forceTransaction, parentSpan : customParentSpan , scope : customScope } = options ;
186157
187158 // If `options.scope` is defined, we use this as as a wrapper,
188159 // If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
189- const wrapper = options . scope
190- ? ( callback : ( ) => Span ) => withScope ( options . scope , callback )
160+ const wrapper = customScope
161+ ? ( callback : ( ) => Span ) => withScope ( customScope , callback )
191162 : customParentSpan !== undefined
192163 ? ( callback : ( ) => Span ) => withActiveSpan ( customParentSpan , callback )
193164 : ( callback : ( ) => Span ) => callback ( ) ;
194165
195166 return wrapper ( ( ) => {
196167 const scope = getCurrentScope ( ) ;
197- const parentSpan = getParentSpan ( scope , customParentSpan ) ;
168+ const parentSpan = getParentSpan ( customScope ?? scope , customParentSpan ) ;
198169 const client = getClient ( ) ;
199170
200171 const missingRequiredParent = options . onlyIfParent && ! parentSpan ;
@@ -342,10 +313,10 @@ function startMissingRequiredParentSpan(scope: Scope, client: Client | undefined
342313}
343314
344315function createChildOrRootSpan ( {
345- parentSpan,
316+ parentSpan : resolvedParentSpan ,
346317 spanArguments,
347318 forceTransaction,
348- scope,
319+ scope : currentScope ,
349320} : {
350321 parentSpan : Span | undefined ;
351322 spanArguments : SentrySpanArguments ;
@@ -354,6 +325,16 @@ function createChildOrRootSpan({
354325} ) : Span {
355326 const isolationScope = getIsolationScope ( ) ;
356327
328+ // Listeners can adjust the scope and the parent right before span creation. The Node SDK uses
329+ // this to turn a remote parent (an incoming trace on the ambient OTel context) into a propagation
330+ // context on a forked scope, so the span continues the incoming trace as a root span.
331+ const spanScope : { scope : Scope ; parentSpan : Span | undefined } = {
332+ scope : currentScope ,
333+ parentSpan : resolvedParentSpan ,
334+ } ;
335+ getClient ( ) ?. emit ( 'prepareSpanScope' , spanScope ) ;
336+ const { scope, parentSpan } = spanScope ;
337+
357338 if ( ! hasSpansEnabled ( ) ) {
358339 const scopePropagationContext = scope . getPropagationContext ( ) ;
359340 const traceId = parentSpan ? parentSpan . spanContext ( ) . traceId : scopePropagationContext . traceId ;
@@ -486,6 +467,11 @@ function getAcs(): AsyncContextStrategy {
486467 return getAsyncContextStrategy ( carrier ) ;
487468}
488469
470+ /**
471+ * Runs the callback with the span active. When the async context strategy bridges to an ambient
472+ * context (OTel), activation must go through it so the span lands on that context and
473+ * instrumentation-created child spans nest under it; the scope alone is not consulted there.
474+ */
489475function _startRootSpan (
490476 spanArguments : SentrySpanArguments ,
491477 scope : Scope ,
@@ -666,8 +652,16 @@ function runCallback<T>(span: Span, makeSpanActive: boolean, callback: () => T,
666652 const wrapper = makeSpanActive
667653 ? ( callback : ( ) => T ) => {
668654 return withActiveSpan ( span , ( ) => {
655+ const scope = getCurrentScope ( ) ;
656+ // The fork made by withActiveSpan is based on the ambient scope. Carry over the
657+ // propagation context captured at span creation, which can continue a remote parent's
658+ // trace the ambient scope knows nothing about. For local parents this is a no-op.
659+ const creationScope = getCapturedScopesOnSpan ( span ) . scope ;
660+ if ( creationScope ) {
661+ scope . setPropagationContext ( creationScope . getPropagationContext ( ) ) ;
662+ }
669663 // Make sure the correct scope is captured on the span, since withActiveSpan forks the scope
670- setCapturedScopesOnSpan ( span , getCurrentScope ( ) , getIsolationScope ( ) ) ;
664+ setCapturedScopesOnSpan ( span , scope , getIsolationScope ( ) ) ;
671665 return callback ( ) ;
672666 } ) ;
673667 }
0 commit comments