1+ /* eslint-disable max-lines */
2+
13import type { ClientOptions , Scope , SentrySpanArguments , Span , SpanTimeInput , StartSpanOptions } from '@sentry/types' ;
24import { generatePropagationContext , logger , propagationContextFromHeaders } from '@sentry/utils' ;
35import type { AsyncContextStrategy } from '../asyncContext/types' ;
@@ -32,40 +34,47 @@ const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__';
3234 * You'll always get a span passed to the callback,
3335 * it may just be a non-recording span if the span is not sampled or if tracing is disabled.
3436 */
35- export function startSpan < T > ( context : StartSpanOptions , callback : ( span : Span ) => T ) : T {
37+ export function startSpan < T > ( options : StartSpanOptions , callback : ( span : Span ) => T ) : T {
3638 const acs = getAcs ( ) ;
3739 if ( acs . startSpan ) {
38- return acs . startSpan ( context , callback ) ;
40+ return acs . startSpan ( options , callback ) ;
3941 }
4042
41- const spanContext = normalizeContext ( context ) ;
42-
43- return withScope ( context . scope , scope => {
44- const parentSpan = getParentSpan ( scope ) ;
45-
46- const shouldSkipSpan = context . onlyIfParent && ! parentSpan ;
47- const activeSpan = shouldSkipSpan
48- ? new SentryNonRecordingSpan ( )
49- : createChildOrRootSpan ( {
50- parentSpan,
51- spanContext,
52- forceTransaction : context . forceTransaction ,
53- scope,
54- } ) ;
55-
56- _setSpanForScope ( scope , activeSpan ) ;
57-
58- return handleCallbackErrors (
59- ( ) => callback ( activeSpan ) ,
60- ( ) => {
61- // Only update the span status if it hasn't been changed yet, and the span is not yet finished
62- const { status } = spanToJSON ( activeSpan ) ;
63- if ( activeSpan . isRecording ( ) && ( ! status || status === 'ok' ) ) {
64- activeSpan . setStatus ( { code : SPAN_STATUS_ERROR , message : 'internal_error' } ) ;
65- }
66- } ,
67- ( ) => activeSpan . end ( ) ,
68- ) ;
43+ const spanArguments = parseSentrySpanArguments ( options ) ;
44+ const { forceTransaction, parentSpan : customParentSpan } = options ;
45+
46+ return withScope ( options . scope , ( ) => {
47+ // If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
48+ const wrapper = getActiveSpanWrapper < T > ( customParentSpan ) ;
49+
50+ return wrapper ( ( ) => {
51+ const scope = getCurrentScope ( ) ;
52+ const parentSpan = getParentSpan ( scope ) ;
53+
54+ const shouldSkipSpan = options . onlyIfParent && ! parentSpan ;
55+ const activeSpan = shouldSkipSpan
56+ ? new SentryNonRecordingSpan ( )
57+ : createChildOrRootSpan ( {
58+ parentSpan,
59+ spanArguments,
60+ forceTransaction,
61+ scope,
62+ } ) ;
63+
64+ _setSpanForScope ( scope , activeSpan ) ;
65+
66+ return handleCallbackErrors (
67+ ( ) => callback ( activeSpan ) ,
68+ ( ) => {
69+ // Only update the span status if it hasn't been changed yet, and the span is not yet finished
70+ const { status } = spanToJSON ( activeSpan ) ;
71+ if ( activeSpan . isRecording ( ) && ( ! status || status === 'ok' ) ) {
72+ activeSpan . setStatus ( { code : SPAN_STATUS_ERROR , message : 'internal_error' } ) ;
73+ }
74+ } ,
75+ ( ) => activeSpan . end ( ) ,
76+ ) ;
77+ } ) ;
6978 } ) ;
7079}
7180
@@ -79,43 +88,50 @@ export function startSpan<T>(context: StartSpanOptions, callback: (span: Span) =
7988 * You'll always get a span passed to the callback,
8089 * it may just be a non-recording span if the span is not sampled or if tracing is disabled.
8190 */
82- export function startSpanManual < T > ( context : StartSpanOptions , callback : ( span : Span , finish : ( ) => void ) => T ) : T {
91+ export function startSpanManual < T > ( options : StartSpanOptions , callback : ( span : Span , finish : ( ) => void ) => T ) : T {
8392 const acs = getAcs ( ) ;
8493 if ( acs . startSpanManual ) {
85- return acs . startSpanManual ( context , callback ) ;
94+ return acs . startSpanManual ( options , callback ) ;
8695 }
8796
88- const spanContext = normalizeContext ( context ) ;
89-
90- return withScope ( context . scope , scope => {
91- const parentSpan = getParentSpan ( scope ) ;
92-
93- const shouldSkipSpan = context . onlyIfParent && ! parentSpan ;
94- const activeSpan = shouldSkipSpan
95- ? new SentryNonRecordingSpan ( )
96- : createChildOrRootSpan ( {
97- parentSpan,
98- spanContext,
99- forceTransaction : context . forceTransaction ,
100- scope,
101- } ) ;
102-
103- _setSpanForScope ( scope , activeSpan ) ;
104-
105- function finishAndSetSpan ( ) : void {
106- activeSpan . end ( ) ;
107- }
108-
109- return handleCallbackErrors (
110- ( ) => callback ( activeSpan , finishAndSetSpan ) ,
111- ( ) => {
112- // Only update the span status if it hasn't been changed yet, and the span is not yet finished
113- const { status } = spanToJSON ( activeSpan ) ;
114- if ( activeSpan . isRecording ( ) && ( ! status || status === 'ok' ) ) {
115- activeSpan . setStatus ( { code : SPAN_STATUS_ERROR , message : 'internal_error' } ) ;
116- }
117- } ,
118- ) ;
97+ const spanArguments = parseSentrySpanArguments ( options ) ;
98+ const { forceTransaction, parentSpan : customParentSpan } = options ;
99+
100+ return withScope ( options . scope , ( ) => {
101+ // If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
102+ const wrapper = getActiveSpanWrapper < T > ( customParentSpan ) ;
103+
104+ return wrapper ( ( ) => {
105+ const scope = getCurrentScope ( ) ;
106+ const parentSpan = getParentSpan ( scope ) ;
107+
108+ const shouldSkipSpan = options . onlyIfParent && ! parentSpan ;
109+ const activeSpan = shouldSkipSpan
110+ ? new SentryNonRecordingSpan ( )
111+ : createChildOrRootSpan ( {
112+ parentSpan,
113+ spanArguments,
114+ forceTransaction,
115+ scope,
116+ } ) ;
117+
118+ _setSpanForScope ( scope , activeSpan ) ;
119+
120+ function finishAndSetSpan ( ) : void {
121+ activeSpan . end ( ) ;
122+ }
123+
124+ return handleCallbackErrors (
125+ ( ) => callback ( activeSpan , finishAndSetSpan ) ,
126+ ( ) => {
127+ // Only update the span status if it hasn't been changed yet, and the span is not yet finished
128+ const { status } = spanToJSON ( activeSpan ) ;
129+ if ( activeSpan . isRecording ( ) && ( ! status || status === 'ok' ) ) {
130+ activeSpan . setStatus ( { code : SPAN_STATUS_ERROR , message : 'internal_error' } ) ;
131+ }
132+ } ,
133+ ) ;
134+ } ) ;
119135 } ) ;
120136}
121137
@@ -128,28 +144,39 @@ export function startSpanManual<T>(context: StartSpanOptions, callback: (span: S
128144 * This function will always return a span,
129145 * it may just be a non-recording span if the span is not sampled or if tracing is disabled.
130146 */
131- export function startInactiveSpan ( context : StartSpanOptions ) : Span {
147+ export function startInactiveSpan ( options : StartSpanOptions ) : Span {
132148 const acs = getAcs ( ) ;
133149 if ( acs . startInactiveSpan ) {
134- return acs . startInactiveSpan ( context ) ;
150+ return acs . startInactiveSpan ( options ) ;
135151 }
136152
137- const spanContext = normalizeContext ( context ) ;
153+ const spanArguments = parseSentrySpanArguments ( options ) ;
154+ const { forceTransaction, parentSpan : customParentSpan } = options ;
138155
139- const scope = context . scope || getCurrentScope ( ) ;
140- const parentSpan = getParentSpan ( scope ) ;
156+ // If `options.scope` is defined, we use this as as a wrapper,
157+ // If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
158+ const wrapper = options . scope
159+ ? ( callback : ( ) => Span ) => withScope ( options . scope , callback )
160+ : customParentSpan
161+ ? ( callback : ( ) => Span ) => withActiveSpan ( customParentSpan , callback )
162+ : ( callback : ( ) => Span ) => callback ( ) ;
141163
142- const shouldSkipSpan = context . onlyIfParent && ! parentSpan ;
164+ return wrapper ( ( ) => {
165+ const scope = getCurrentScope ( ) ;
166+ const parentSpan = getParentSpan ( scope ) ;
143167
144- if ( shouldSkipSpan ) {
145- return new SentryNonRecordingSpan ( ) ;
146- }
168+ const shouldSkipSpan = options . onlyIfParent && ! parentSpan ;
169+
170+ if ( shouldSkipSpan ) {
171+ return new SentryNonRecordingSpan ( ) ;
172+ }
147173
148- return createChildOrRootSpan ( {
149- parentSpan,
150- spanContext,
151- forceTransaction : context . forceTransaction ,
152- scope,
174+ return createChildOrRootSpan ( {
175+ parentSpan,
176+ spanArguments,
177+ forceTransaction,
178+ scope,
179+ } ) ;
153180 } ) ;
154181}
155182
@@ -239,12 +266,12 @@ export function startNewTrace<T>(callback: () => T): T {
239266
240267function createChildOrRootSpan ( {
241268 parentSpan,
242- spanContext ,
269+ spanArguments ,
243270 forceTransaction,
244271 scope,
245272} : {
246273 parentSpan : SentrySpan | undefined ;
247- spanContext : SentrySpanArguments ;
274+ spanArguments : SentrySpanArguments ;
248275 forceTransaction ?: boolean ;
249276 scope : Scope ;
250277} ) : Span {
@@ -256,7 +283,7 @@ function createChildOrRootSpan({
256283
257284 let span : Span ;
258285 if ( parentSpan && ! forceTransaction ) {
259- span = _startChildSpan ( parentSpan , scope , spanContext ) ;
286+ span = _startChildSpan ( parentSpan , scope , spanArguments ) ;
260287 addChildSpanToSpan ( parentSpan , span ) ;
261288 } else if ( parentSpan ) {
262289 // If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
@@ -268,7 +295,7 @@ function createChildOrRootSpan({
268295 {
269296 traceId,
270297 parentSpanId,
271- ...spanContext ,
298+ ...spanArguments ,
272299 } ,
273300 scope ,
274301 parentSampled ,
@@ -290,7 +317,7 @@ function createChildOrRootSpan({
290317 {
291318 traceId,
292319 parentSpanId,
293- ...spanContext ,
320+ ...spanArguments ,
294321 } ,
295322 scope ,
296323 parentSampled ,
@@ -312,19 +339,17 @@ function createChildOrRootSpan({
312339 * This converts StartSpanOptions to SentrySpanArguments.
313340 * For the most part (for now) we accept the same options,
314341 * but some of them need to be transformed.
315- *
316- * Eventually the StartSpanOptions will be more aligned with OpenTelemetry.
317342 */
318- function normalizeContext ( context : StartSpanOptions ) : SentrySpanArguments {
319- const exp = context . experimental || { } ;
343+ function parseSentrySpanArguments ( options : StartSpanOptions ) : SentrySpanArguments {
344+ const exp = options . experimental || { } ;
320345 const initialCtx : SentrySpanArguments = {
321346 isStandalone : exp . standalone ,
322- ...context ,
347+ ...options ,
323348 } ;
324349
325- if ( context . startTime ) {
350+ if ( options . startTime ) {
326351 const ctx : SentrySpanArguments & { startTime ?: SpanTimeInput } = { ...initialCtx } ;
327- ctx . startTimestamp = spanTimeInputToSeconds ( context . startTime ) ;
352+ ctx . startTimestamp = spanTimeInputToSeconds ( options . startTime ) ;
328353 delete ctx . startTime ;
329354 return ctx ;
330355 }
@@ -419,3 +444,11 @@ function getParentSpan(scope: Scope): SentrySpan | undefined {
419444
420445 return span ;
421446}
447+
448+ function getActiveSpanWrapper < T > ( parentSpan ?: Span ) : ( callback : ( ) => T ) => T {
449+ return parentSpan
450+ ? ( callback : ( ) => T ) => {
451+ return withActiveSpan ( parentSpan , callback ) ;
452+ }
453+ : ( callback : ( ) => T ) => callback ( ) ;
454+ }
0 commit comments