1
1
import type { Baggage , Context , SpanContext , TextMapGetter , TextMapSetter } from '@opentelemetry/api' ;
2
2
import { TraceFlags , propagation , trace } from '@opentelemetry/api' ;
3
3
import { TraceState , W3CBaggagePropagator , isTracingSuppressed } from '@opentelemetry/core' ;
4
- import { getClient , getDynamicSamplingContextFromClient } from '@sentry/core' ;
4
+ import { getClient , getCurrentScope , getDynamicSamplingContextFromClient , getIsolationScope } from '@sentry/core' ;
5
5
import type { DynamicSamplingContext , PropagationContext } from '@sentry/types' ;
6
6
import {
7
7
SENTRY_BAGGAGE_KEY_PREFIX ,
@@ -11,28 +11,30 @@ import {
11
11
propagationContextFromHeaders ,
12
12
} from '@sentry/utils' ;
13
13
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
+ } ;
36
38
}
37
39
38
40
/**
@@ -49,10 +51,7 @@ export class SentryPropagator extends W3CBaggagePropagator {
49
51
50
52
let baggage = propagation . getBaggage ( context ) || propagation . createBaggage ( { } ) ;
51
53
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 ) ;
56
55
57
56
if ( dynamicSamplingContext ) {
58
57
baggage = Object . entries ( dynamicSamplingContext ) . reduce < Baggage > ( ( b , [ dscKey , dscValue ] ) => {
@@ -83,15 +82,11 @@ export class SentryPropagator extends W3CBaggagePropagator {
83
82
84
83
const propagationContext = propagationContextFromHeaders ( sentryTraceHeader , maybeBaggageHeader ) ;
85
84
86
- // Add propagation context to context
87
- const contextWithPropagationContext = setPropagationContextOnContext ( context , propagationContext ) ;
88
-
89
85
// 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
+ } ) ;
95
90
96
91
const spanContext : SpanContext = {
97
92
traceId : propagationContext . traceId ,
@@ -101,8 +96,18 @@ export class SentryPropagator extends W3CBaggagePropagator {
101
96
traceState,
102
97
} ;
103
98
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 ) ;
106
111
}
107
112
108
113
/**
@@ -113,13 +118,91 @@ export class SentryPropagator extends W3CBaggagePropagator {
113
118
}
114
119
}
115
120
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. */
117
200
function getDynamicSamplingContext (
118
201
propagationContext : PropagationContext ,
119
202
traceId : string | undefined ,
120
203
) : Partial < DynamicSamplingContext > | undefined {
121
204
// If we have a DSC on the propagation context, we just use it
122
- if ( propagationContext . dsc ) {
205
+ if ( propagationContext ? .dsc ) {
123
206
return propagationContext . dsc ;
124
207
}
125
208
@@ -132,30 +215,3 @@ function getDynamicSamplingContext(
132
215
133
216
return undefined ;
134
217
}
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