1- import type { Attributes , Context , Span } from '@opentelemetry/api' ;
1+ import type { Attributes , Context , Span , TraceState as TraceStateInterface } from '@opentelemetry/api' ;
22import { SpanKind } from '@opentelemetry/api' ;
33import { isSpanContextValid , trace } from '@opentelemetry/api' ;
44import { TraceState } from '@opentelemetry/core' ;
@@ -40,16 +40,8 @@ export class SentrySampler implements Sampler {
4040 const parentSpan = trace . getSpan ( context ) ;
4141 const parentContext = parentSpan ?. spanContext ( ) ;
4242
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-
5143 if ( ! hasTracingEnabled ( options ) ) {
52- return { decision : SamplingDecision . NOT_RECORD , traceState } ;
44+ return sentrySamplerNoDecision ( { context , spanAttributes } ) ;
5345 }
5446
5547 // 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 {
5951 spanAttributes [ SEMATTRS_HTTP_METHOD ] &&
6052 ( ! parentSpan || parentContext ?. isRemote )
6153 ) {
62- return { decision : SamplingDecision . NOT_RECORD , traceState } ;
54+ return sentrySamplerNoDecision ( { context , spanAttributes } ) ;
6355 }
6456
6557 const parentSampled = parentSpan ? getParentSampled ( parentSpan , traceId , spanName ) : undefined ;
@@ -76,7 +68,7 @@ export class SentrySampler implements Sampler {
7668 mutableSamplingDecision ,
7769 ) ;
7870 if ( ! mutableSamplingDecision . decision ) {
79- return { decision : SamplingDecision . NOT_RECORD , traceState : traceState } ;
71+ return sentrySamplerNoDecision ( { context , spanAttributes } ) ;
8072 }
8173
8274 const [ sampled , sampleRate ] = sampleSpan ( options , {
@@ -96,25 +88,22 @@ export class SentrySampler implements Sampler {
9688 const method = `${ spanAttributes [ SEMATTRS_HTTP_METHOD ] } ` . toUpperCase ( ) ;
9789 if ( method === 'OPTIONS' || method === 'HEAD' ) {
9890 DEBUG_BUILD && logger . log ( `[Tracing] Not sampling span because HTTP method is '${ method } ' for ${ spanName } ` ) ;
91+
9992 return {
100- decision : SamplingDecision . NOT_RECORD ,
93+ ... sentrySamplerNotSampled ( { context , spanAttributes } ) ,
10194 attributes,
102- traceState : traceState . set ( SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING , '1' ) ,
10395 } ;
10496 }
10597
10698 if ( ! sampled ) {
10799 return {
108- decision : SamplingDecision . NOT_RECORD ,
100+ ... sentrySamplerNotSampled ( { context , spanAttributes } ) ,
109101 attributes,
110- traceState : traceState . set ( SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING , '1' ) ,
111102 } ;
112103 }
113-
114104 return {
115- decision : SamplingDecision . RECORD_AND_SAMPLED ,
105+ ... sentrySamplerSampled ( { context , spanAttributes } ) ,
116106 attributes,
117- traceState,
118107 } ;
119108 }
120109
@@ -152,3 +141,55 @@ function getParentSampled(parentSpan: Span, traceId: string, spanName: string):
152141
153142 return undefined ;
154143}
144+
145+ /**
146+ * Returns a SamplingResult that indicates that a span was not sampled, but no definite decision was made yet.
147+ * This indicates to downstream SDKs that they may make their own decision.
148+ */
149+ export function sentrySamplerNoDecision ( {
150+ context,
151+ spanAttributes,
152+ } : { context : Context ; spanAttributes : SpanAttributes } ) : SamplingResult {
153+ const traceState = getBaseTraceState ( context , spanAttributes ) ;
154+
155+ return { decision : SamplingDecision . NOT_RECORD , traceState } ;
156+ }
157+
158+ /**
159+ * Returns a SamplingResult that indicates that a span was not sampled.
160+ */
161+ export function sentrySamplerNotSampled ( {
162+ context,
163+ spanAttributes,
164+ } : { context : Context ; spanAttributes : SpanAttributes } ) : SamplingResult {
165+ const traceState = getBaseTraceState ( context , spanAttributes ) . set ( SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING , '1' ) ;
166+
167+ return { decision : SamplingDecision . NOT_RECORD , traceState } ;
168+ }
169+
170+ /**
171+ * Returns a SamplingResult that indicates that a span was sampled.
172+ */
173+ export function sentrySamplerSampled ( {
174+ context,
175+ spanAttributes,
176+ } : { context : Context ; spanAttributes : SpanAttributes } ) : SamplingResult {
177+ const traceState = getBaseTraceState ( context , spanAttributes ) ;
178+
179+ return { decision : SamplingDecision . RECORD_AND_SAMPLED , traceState } ;
180+ }
181+
182+ function getBaseTraceState ( context : Context , spanAttributes : SpanAttributes ) : TraceStateInterface {
183+ const parentSpan = trace . getSpan ( context ) ;
184+ const parentContext = parentSpan ?. spanContext ( ) ;
185+
186+ let traceState = parentContext ?. traceState || new TraceState ( ) ;
187+
188+ // We always keep the URL on the trace state, so we can access it in the propagator
189+ const url = spanAttributes [ SEMATTRS_HTTP_URL ] ;
190+ if ( url && typeof url === 'string' ) {
191+ traceState = traceState . set ( SENTRY_TRACE_STATE_URL , url ) ;
192+ }
193+
194+ return traceState ;
195+ }
0 commit comments