@@ -12,7 +12,7 @@ import {
1212 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
1313} from '../semanticAttributes' ;
1414import type { ClientOptions } from '../types/options' ;
15- import type { SentrySpanArguments , Span , SpanContextData , SpanTimeInput } from '../types/span' ;
15+ import type { SentrySpanArguments , Span , SpanTimeInput } from '../types/span' ;
1616import type { StartSpanOptions } from '../types/startSpanOptions' ;
1717import { baggageHeaderToDynamicSamplingContext } from '../utils/baggage' ;
1818import { debug } from '../utils/debug-logger' ;
@@ -31,7 +31,6 @@ import {
3131 spanIsSampled ,
3232 spanTimeInputToSeconds ,
3333 spanToStaticSpanJSON ,
34- TRACE_FLAG_SAMPLED ,
3534} from '../utils/spanUtils' ;
3635import { propagationContextFromHeaders , shouldContinueTrace } from '../utils/tracing' ;
3736import { freezeDscOnSpan , getDynamicSamplingContextFromSpan } from './dynamicSamplingContext' ;
@@ -326,12 +325,15 @@ function createChildOrRootSpan({
326325} ) : Span {
327326 const isolationScope = getIsolationScope ( ) ;
328327
329- // A remote parent is an incoming trace on the ambient OTel context, set by the propagator. It
330- // cannot be used as a local parent span. Instead, we continue its trace through the propagation
331- // context and start a root span, like `SentryTracer` does for remote parents.
332- const remoteParentSpan = resolvedParentSpan ?. spanContext ( ) . isRemote ? resolvedParentSpan : undefined ;
333- const scope = remoteParentSpan ? scopeWithRemoteParentPropagation ( currentScope , remoteParentSpan ) : currentScope ;
334- const parentSpan = remoteParentSpan ? undefined : resolvedParentSpan ;
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 ;
335337
336338 if ( ! hasSpansEnabled ( ) ) {
337339 const scopePropagationContext = scope . getPropagationContext ( ) ;
@@ -585,61 +587,6 @@ function _startChildSpan(
585587 return childSpan ;
586588}
587589
588- /**
589- * Fork the scope with a propagation context continuing the remote parent's trace, so the root span
590- * created from it picks up the incoming trace id, parent span id, and sampling decision. Mirrors
591- * `SentryTracer._startRootSpanWithRemoteParent` in `@sentry/opentelemetry`.
592- */
593- function scopeWithRemoteParentPropagation ( scope : Scope , remoteParentSpan : Span ) : Scope {
594- const { spanId, traceId, traceState } = remoteParentSpan . spanContext ( ) ;
595- const dsc = getDynamicSamplingContextFromSpan ( remoteParentSpan ) ;
596- const sampleRand = typeof dsc . sample_rand === 'string' ? Number ( dsc . sample_rand ) : undefined ;
597-
598- // Only freeze the DSC when the remote parent actually carried one (i.e. there was incoming
599- // baggage). Otherwise leave it unset so it is derived dynamically from the span — picking up the
600- // span's `transaction` name and the generated `sample_rand`.
601- const hasIncomingDsc = ! ! traceState ?. get ( 'sentry.dsc' ) ;
602-
603- const forkedScope = scope . clone ( ) ;
604- forkedScope . setPropagationContext ( {
605- traceId,
606- parentSpanId : spanId ,
607- sampled : getSamplingDecisionFromRemoteSpanContext ( remoteParentSpan . spanContext ( ) ) ,
608- dsc : hasIncomingDsc ? dsc : undefined ,
609- sampleRand : typeof sampleRand === 'number' && ! Number . isNaN ( sampleRand ) ? sampleRand : safeMathRandom ( ) ,
610- } ) ;
611- _setSpanForScope ( forkedScope , undefined ) ;
612- return forkedScope ;
613- }
614-
615- /**
616- * OpenTelemetry only knows a SAMPLED or NONE decision, but for us it is important to differentiate
617- * between unsampled and unset (deferred). Both arrive as `traceFlags: NONE`; the Sentry trace state
618- * and the DSC disambiguate. Port of `getSamplingDecision` in `@sentry/opentelemetry`.
619- */
620- function getSamplingDecisionFromRemoteSpanContext ( spanContext : SpanContextData ) : boolean | undefined {
621- const { traceFlags, traceState } = spanContext ;
622-
623- if ( traceFlags === TRACE_FLAG_SAMPLED ) {
624- return true ;
625- }
626-
627- if ( traceState ?. get ( 'sentry.sampled_not_recording' ) === '1' ) {
628- return false ;
629- }
630-
631- const dscString = traceState ?. get ( 'sentry.dsc' ) ;
632- const dsc = dscString ? baggageHeaderToDynamicSamplingContext ( dscString ) : undefined ;
633- if ( dsc ?. sampled === 'true' ) {
634- return true ;
635- }
636- if ( dsc ?. sampled === 'false' ) {
637- return false ;
638- }
639-
640- return undefined ;
641- }
642-
643590function getParentSpan ( scope : Scope , customParentSpan : Span | null | undefined ) : Span | undefined {
644591 // always use the passed in span directly
645592 if ( customParentSpan ) {
0 commit comments