1
- import type { Attributes , Context , Span } from '@opentelemetry/api' ;
1
+ import type { Attributes , Context , Span , TraceState as TraceStateInterface } from '@opentelemetry/api' ;
2
2
import { SpanKind } from '@opentelemetry/api' ;
3
3
import { isSpanContextValid , trace } from '@opentelemetry/api' ;
4
4
import { TraceState } from '@opentelemetry/core' ;
@@ -40,16 +40,8 @@ export class SentrySampler implements Sampler {
40
40
const parentSpan = trace . getSpan ( context ) ;
41
41
const parentContext = parentSpan ?. spanContext ( ) ;
42
42
43
- let traceState = parentContext ?. traceState || new TraceState ( ) ;
44
-
45
- // We always keep the URL on the trace state, so we can access it in the propagator
46
- const url = spanAttributes [ SEMATTRS_HTTP_URL ] ;
47
- if ( url && typeof url === 'string' ) {
48
- traceState = traceState . set ( SENTRY_TRACE_STATE_URL , url ) ;
49
- }
50
-
51
43
if ( ! hasTracingEnabled ( options ) ) {
52
- return { decision : SamplingDecision . NOT_RECORD , traceState } ;
44
+ return wrapSamplingDecision ( { decision : undefined , context , spanAttributes } ) ;
53
45
}
54
46
55
47
// If we have a http.client span that has no local parent, we never want to sample it
@@ -59,7 +51,7 @@ export class SentrySampler implements Sampler {
59
51
spanAttributes [ SEMATTRS_HTTP_METHOD ] &&
60
52
( ! parentSpan || parentContext ?. isRemote )
61
53
) {
62
- return { decision : SamplingDecision . NOT_RECORD , traceState } ;
54
+ return wrapSamplingDecision ( { decision : undefined , context , spanAttributes } ) ;
63
55
}
64
56
65
57
const parentSampled = parentSpan ? getParentSampled ( parentSpan , traceId , spanName ) : undefined ;
@@ -76,7 +68,7 @@ export class SentrySampler implements Sampler {
76
68
mutableSamplingDecision ,
77
69
) ;
78
70
if ( ! mutableSamplingDecision . decision ) {
79
- return { decision : SamplingDecision . NOT_RECORD , traceState : traceState } ;
71
+ return wrapSamplingDecision ( { decision : undefined , context , spanAttributes } ) ;
80
72
}
81
73
82
74
const [ sampled , sampleRate ] = sampleSpan ( options , {
@@ -96,25 +88,22 @@ export class SentrySampler implements Sampler {
96
88
const method = `${ spanAttributes [ SEMATTRS_HTTP_METHOD ] } ` . toUpperCase ( ) ;
97
89
if ( method === 'OPTIONS' || method === 'HEAD' ) {
98
90
DEBUG_BUILD && logger . log ( `[Tracing] Not sampling span because HTTP method is '${ method } ' for ${ spanName } ` ) ;
91
+
99
92
return {
100
- decision : SamplingDecision . NOT_RECORD ,
93
+ ... wrapSamplingDecision ( { decision : SamplingDecision . NOT_RECORD , context , spanAttributes } ) ,
101
94
attributes,
102
- traceState : traceState . set ( SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING , '1' ) ,
103
95
} ;
104
96
}
105
97
106
98
if ( ! sampled ) {
107
99
return {
108
- decision : SamplingDecision . NOT_RECORD ,
100
+ ... wrapSamplingDecision ( { decision : SamplingDecision . NOT_RECORD , context , spanAttributes } ) ,
109
101
attributes,
110
- traceState : traceState . set ( SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING , '1' ) ,
111
102
} ;
112
103
}
113
-
114
104
return {
115
- decision : SamplingDecision . RECORD_AND_SAMPLED ,
105
+ ... wrapSamplingDecision ( { decision : SamplingDecision . RECORD_AND_SAMPLED , context , spanAttributes } ) ,
116
106
attributes,
117
- traceState,
118
107
} ;
119
108
}
120
109
@@ -152,3 +141,43 @@ function getParentSampled(parentSpan: Span, traceId: string, spanName: string):
152
141
153
142
return undefined ;
154
143
}
144
+
145
+ /**
146
+ * Wrap a sampling decision with data that Sentry needs to work properly with it.
147
+ * If you pass `decision: undefined`, it will be treated as `NOT_RECORDING`, but in contrast to passing `NOT_RECORDING`
148
+ * it will not propagate this decision to downstream Sentry SDKs.
149
+ */
150
+ export function wrapSamplingDecision ( {
151
+ decision,
152
+ context,
153
+ spanAttributes,
154
+ } : { decision : SamplingDecision | undefined ; context : Context ; spanAttributes : SpanAttributes } ) : SamplingResult {
155
+ const traceState = getBaseTraceState ( context , spanAttributes ) ;
156
+
157
+ // If the decision is undefined, we treat it as NOT_RECORDING, but we don't propagate this decision to downstream SDKs
158
+ // Which is done by not setting `SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING` traceState
159
+ if ( decision == undefined ) {
160
+ return { decision : SamplingDecision . NOT_RECORD , traceState } ;
161
+ }
162
+
163
+ if ( decision === SamplingDecision . NOT_RECORD ) {
164
+ return { decision, traceState : traceState . set ( SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING , '1' ) } ;
165
+ }
166
+
167
+ return { decision, traceState } ;
168
+ }
169
+
170
+ function getBaseTraceState ( context : Context , spanAttributes : SpanAttributes ) : TraceStateInterface {
171
+ const parentSpan = trace . getSpan ( context ) ;
172
+ const parentContext = parentSpan ?. spanContext ( ) ;
173
+
174
+ let traceState = parentContext ?. traceState || new TraceState ( ) ;
175
+
176
+ // We always keep the URL on the trace state, so we can access it in the propagator
177
+ const url = spanAttributes [ SEMATTRS_HTTP_URL ] ;
178
+ if ( url && typeof url === 'string' ) {
179
+ traceState = traceState . set ( SENTRY_TRACE_STATE_URL , url ) ;
180
+ }
181
+
182
+ return traceState ;
183
+ }
0 commit comments