11import type { Baggage , Context , SpanContext , TextMapGetter , TextMapSetter } from '@opentelemetry/api' ;
22import { TraceFlags , propagation , trace } from '@opentelemetry/api' ;
33import { TraceState , W3CBaggagePropagator , isTracingSuppressed } from '@opentelemetry/core' ;
4- import { getClient , getDynamicSamplingContextFromClient } from '@sentry/core' ;
4+ import { getClient , getCurrentScope , getDynamicSamplingContextFromClient , getIsolationScope } from '@sentry/core' ;
55import type { DynamicSamplingContext , PropagationContext } from '@sentry/types' ;
66import {
77 SENTRY_BAGGAGE_KEY_PREFIX ,
@@ -11,28 +11,30 @@ import {
1111 propagationContextFromHeaders ,
1212} from '@sentry/utils' ;
1313
14- import { SENTRY_BAGGAGE_HEADER , SENTRY_TRACE_HEADER , SENTRY_TRACE_STATE_DSC } from './constants' ;
15- import { getPropagationContextFromContext , setPropagationContextOnContext } from './utils/contextData' ;
16-
17- function getDynamicSamplingContextFromContext ( context : Context ) : Partial < DynamicSamplingContext > | undefined {
18- // If possible, we want to take the DSC from the active span
19- // That should take precedence over the DSC from the propagation context
20- const activeSpan = trace . getSpan ( context ) ;
21- const traceStateDsc = activeSpan ?. spanContext ( ) . traceState ?. get ( SENTRY_TRACE_STATE_DSC ) ;
22- const dscOnSpan = traceStateDsc ? baggageHeaderToDynamicSamplingContext ( traceStateDsc ) : undefined ;
23-
24- if ( dscOnSpan ) {
25- return dscOnSpan ;
26- }
27-
28- const propagationContext = getPropagationContextFromContext ( context ) ;
29-
30- if ( propagationContext ) {
31- const { traceId } = getSentryTraceData ( context , propagationContext ) ;
32- return getDynamicSamplingContext ( propagationContext , traceId ) ;
33- }
34-
35- return undefined ;
14+ import {
15+ SENTRY_BAGGAGE_HEADER ,
16+ SENTRY_TRACE_HEADER ,
17+ SENTRY_TRACE_STATE_DSC ,
18+ SENTRY_TRACE_STATE_PARENT_SPAN_ID ,
19+ } from './constants' ;
20+ import { getScopesFromContext , setScopesOnContext } from './utils/contextData' ;
21+
22+ /** Get the Sentry propagation context from a span context. */
23+ export function getPropagationContextFromSpanContext ( spanContext : SpanContext ) : PropagationContext {
24+ const { traceId, spanId, traceFlags, traceState } = spanContext ;
25+
26+ const dscString = traceState ? traceState . get ( SENTRY_TRACE_STATE_DSC ) : undefined ;
27+ const dsc = dscString ? baggageHeaderToDynamicSamplingContext ( dscString ) : undefined ;
28+ const parentSpanId = traceState ? traceState . get ( SENTRY_TRACE_STATE_PARENT_SPAN_ID ) : undefined ;
29+ const sampled = traceFlags === TraceFlags . SAMPLED ;
30+
31+ return {
32+ traceId,
33+ spanId,
34+ sampled,
35+ parentSpanId,
36+ dsc,
37+ } ;
3638}
3739
3840/**
@@ -49,10 +51,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
4951
5052 let baggage = propagation . getBaggage ( context ) || propagation . createBaggage ( { } ) ;
5153
52- const propagationContext = getPropagationContextFromContext ( context ) ;
53- const { spanId, traceId, sampled } = getSentryTraceData ( context , propagationContext ) ;
54-
55- const dynamicSamplingContext = getDynamicSamplingContextFromContext ( context ) ;
54+ const { dynamicSamplingContext, traceId, spanId, sampled } = getInjectionData ( context ) ;
5655
5756 if ( dynamicSamplingContext ) {
5857 baggage = Object . entries ( dynamicSamplingContext ) . reduce < Baggage > ( ( b , [ dscKey , dscValue ] ) => {
@@ -83,15 +82,11 @@ export class SentryPropagator extends W3CBaggagePropagator {
8382
8483 const propagationContext = propagationContextFromHeaders ( sentryTraceHeader , maybeBaggageHeader ) ;
8584
86- // Add propagation context to context
87- const contextWithPropagationContext = setPropagationContextOnContext ( context , propagationContext ) ;
88-
8985 // We store the DSC as OTEL trace state on the span context
90- const dscString = propagationContext . dsc
91- ? dynamicSamplingContextToSentryBaggageHeader ( propagationContext . dsc )
92- : undefined ;
93-
94- const traceState = dscString ? new TraceState ( ) . set ( SENTRY_TRACE_STATE_DSC , dscString ) : undefined ;
86+ const traceState = makeTraceState ( {
87+ parentSpanId : propagationContext . parentSpanId ,
88+ dsc : propagationContext . dsc ,
89+ } ) ;
9590
9691 const spanContext : SpanContext = {
9792 traceId : propagationContext . traceId ,
@@ -101,8 +96,18 @@ export class SentryPropagator extends W3CBaggagePropagator {
10196 traceState,
10297 } ;
10398
104- // Add remote parent span context
105- return trace . setSpanContext ( contextWithPropagationContext , spanContext ) ;
99+ // Add remote parent span context,
100+ const ctxWithSpanContext = trace . setSpanContext ( context , spanContext ) ;
101+
102+ // Also update the scope on the context (to be sure this is picked up everywhere)
103+ const scopes = getScopesFromContext ( ctxWithSpanContext ) ;
104+ const newScopes = {
105+ scope : scopes ? scopes . scope . clone ( ) : getCurrentScope ( ) . clone ( ) ,
106+ isolationScope : scopes ? scopes . isolationScope : getIsolationScope ( ) ,
107+ } ;
108+ newScopes . scope . setPropagationContext ( propagationContext ) ;
109+
110+ return setScopesOnContext ( ctxWithSpanContext , newScopes ) ;
106111 }
107112
108113 /**
@@ -113,13 +118,91 @@ export class SentryPropagator extends W3CBaggagePropagator {
113118 }
114119}
115120
116- /** Get the DSC. */
121+ /** Exported for tests. */
122+ export function makeTraceState ( {
123+ parentSpanId,
124+ dsc,
125+ } : { parentSpanId ?: string ; dsc ?: Partial < DynamicSamplingContext > } ) : TraceState | undefined {
126+ if ( ! parentSpanId && ! dsc ) {
127+ return undefined ;
128+ }
129+
130+ // We store the DSC as OTEL trace state on the span context
131+ const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader ( dsc ) : undefined ;
132+
133+ const traceStateBase = parentSpanId
134+ ? new TraceState ( ) . set ( SENTRY_TRACE_STATE_PARENT_SPAN_ID , parentSpanId )
135+ : new TraceState ( ) ;
136+
137+ return dscString ? traceStateBase . set ( SENTRY_TRACE_STATE_DSC , dscString ) : traceStateBase ;
138+ }
139+
140+ function getInjectionData ( context : Context ) : {
141+ dynamicSamplingContext : Partial < DynamicSamplingContext > | undefined ;
142+ traceId : string | undefined ;
143+ spanId : string | undefined ;
144+ sampled : boolean | undefined ;
145+ } {
146+ const span = trace . getSpan ( context ) ;
147+ const spanIsRemote = span ?. spanContext ( ) . isRemote ;
148+
149+ // If we have a local span, we can just pick everything from it
150+ if ( span && ! spanIsRemote ) {
151+ const spanContext = span . spanContext ( ) ;
152+ const propagationContext = getPropagationContextFromSpanContext ( spanContext ) ;
153+ const dynamicSamplingContext = getDynamicSamplingContext ( propagationContext , spanContext . traceId ) ;
154+ return {
155+ dynamicSamplingContext,
156+ traceId : spanContext . traceId ,
157+ spanId : spanContext . spanId ,
158+ sampled : spanContext . traceFlags === TraceFlags . SAMPLED ,
159+ } ;
160+ }
161+
162+ // Else we try to use the propagation context from the scope
163+ const scope = getScopesFromContext ( context ) ?. scope ;
164+ if ( scope ) {
165+ const propagationContext = scope . getPropagationContext ( ) ;
166+ const dynamicSamplingContext = getDynamicSamplingContext ( propagationContext , propagationContext . traceId ) ;
167+ return {
168+ dynamicSamplingContext,
169+ traceId : propagationContext . traceId ,
170+ spanId : propagationContext . spanId ,
171+ sampled : propagationContext . sampled ,
172+ } ;
173+ }
174+
175+ // Else, we look at the remote span context
176+ const spanContext = trace . getSpanContext ( context ) ;
177+ if ( spanContext ) {
178+ const propagationContext = getPropagationContextFromSpanContext ( spanContext ) ;
179+ const dynamicSamplingContext = getDynamicSamplingContext ( propagationContext , spanContext . traceId ) ;
180+
181+ return {
182+ dynamicSamplingContext,
183+ traceId : spanContext . traceId ,
184+ spanId : spanContext . spanId ,
185+ sampled : spanContext . traceFlags === TraceFlags . SAMPLED ,
186+ } ;
187+ }
188+
189+ // If we have neither, there is nothing much we can do, but that should not happen usually
190+ // Unless there is a detached OTEL context being passed around
191+ return {
192+ dynamicSamplingContext : undefined ,
193+ traceId : undefined ,
194+ spanId : undefined ,
195+ sampled : undefined ,
196+ } ;
197+ }
198+
199+ /** Get the DSC from a context, or fall back to use the one from the client. */
117200function getDynamicSamplingContext (
118201 propagationContext : PropagationContext ,
119202 traceId : string | undefined ,
120203) : Partial < DynamicSamplingContext > | undefined {
121204 // If we have a DSC on the propagation context, we just use it
122- if ( propagationContext . dsc ) {
205+ if ( propagationContext ? .dsc ) {
123206 return propagationContext . dsc ;
124207 }
125208
@@ -132,30 +215,3 @@ function getDynamicSamplingContext(
132215
133216 return undefined ;
134217}
135-
136- /** Get the trace data for propagation. */
137- function getSentryTraceData (
138- context : Context ,
139- propagationContext : PropagationContext | undefined ,
140- ) : {
141- spanId : string | undefined ;
142- traceId : string | undefined ;
143- sampled : boolean | undefined ;
144- } {
145- const span = trace . getSpan ( context ) ;
146- const spanContext = span && span . spanContext ( ) ;
147-
148- const traceId = spanContext ? spanContext . traceId : propagationContext ?. traceId ;
149-
150- // We have a few scenarios here:
151- // If we have an active span, and it is _not_ remote, we just use the span's ID
152- // If we have an active span that is remote, we do not want to use the spanId, as we don't want to attach it to the parent span
153- // If `isRemote === true`, the span is bascially virtual
154- // If we don't have a local active span, we use the generated spanId from the propagationContext
155- const spanId = spanContext && ! spanContext . isRemote ? spanContext . spanId : propagationContext ?. spanId ;
156-
157- // eslint-disable-next-line no-bitwise
158- const sampled = spanContext ? Boolean ( spanContext . traceFlags & TraceFlags . SAMPLED ) : propagationContext ?. sampled ;
159-
160- return { traceId, spanId, sampled } ;
161- }
0 commit comments