1
+ /* eslint-disable max-lines */
2
+
1
3
import type { ClientOptions , Scope , SentrySpanArguments , Span , SpanTimeInput , StartSpanOptions } from '@sentry/types' ;
2
4
import { generatePropagationContext , logger , propagationContextFromHeaders } from '@sentry/utils' ;
3
5
import type { AsyncContextStrategy } from '../asyncContext/types' ;
@@ -32,40 +34,47 @@ const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__';
32
34
* You'll always get a span passed to the callback,
33
35
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
34
36
*/
35
- export function startSpan < T > ( context : StartSpanOptions , callback : ( span : Span ) => T ) : T {
37
+ export function startSpan < T > ( options : StartSpanOptions , callback : ( span : Span ) => T ) : T {
36
38
const acs = getAcs ( ) ;
37
39
if ( acs . startSpan ) {
38
- return acs . startSpan ( context , callback ) ;
40
+ return acs . startSpan ( options , callback ) ;
39
41
}
40
42
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
+ } ) ;
69
78
} ) ;
70
79
}
71
80
@@ -79,43 +88,50 @@ export function startSpan<T>(context: StartSpanOptions, callback: (span: Span) =
79
88
* You'll always get a span passed to the callback,
80
89
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
81
90
*/
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 {
83
92
const acs = getAcs ( ) ;
84
93
if ( acs . startSpanManual ) {
85
- return acs . startSpanManual ( context , callback ) ;
94
+ return acs . startSpanManual ( options , callback ) ;
86
95
}
87
96
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
+ } ) ;
119
135
} ) ;
120
136
}
121
137
@@ -128,28 +144,39 @@ export function startSpanManual<T>(context: StartSpanOptions, callback: (span: S
128
144
* This function will always return a span,
129
145
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
130
146
*/
131
- export function startInactiveSpan ( context : StartSpanOptions ) : Span {
147
+ export function startInactiveSpan ( options : StartSpanOptions ) : Span {
132
148
const acs = getAcs ( ) ;
133
149
if ( acs . startInactiveSpan ) {
134
- return acs . startInactiveSpan ( context ) ;
150
+ return acs . startInactiveSpan ( options ) ;
135
151
}
136
152
137
- const spanContext = normalizeContext ( context ) ;
153
+ const spanArguments = parseSentrySpanArguments ( options ) ;
154
+ const { forceTransaction, parentSpan : customParentSpan } = options ;
138
155
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 ( ) ;
141
163
142
- const shouldSkipSpan = context . onlyIfParent && ! parentSpan ;
164
+ return wrapper ( ( ) => {
165
+ const scope = getCurrentScope ( ) ;
166
+ const parentSpan = getParentSpan ( scope ) ;
143
167
144
- if ( shouldSkipSpan ) {
145
- return new SentryNonRecordingSpan ( ) ;
146
- }
168
+ const shouldSkipSpan = options . onlyIfParent && ! parentSpan ;
169
+
170
+ if ( shouldSkipSpan ) {
171
+ return new SentryNonRecordingSpan ( ) ;
172
+ }
147
173
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
+ } ) ;
153
180
} ) ;
154
181
}
155
182
@@ -239,12 +266,12 @@ export function startNewTrace<T>(callback: () => T): T {
239
266
240
267
function createChildOrRootSpan ( {
241
268
parentSpan,
242
- spanContext ,
269
+ spanArguments ,
243
270
forceTransaction,
244
271
scope,
245
272
} : {
246
273
parentSpan : SentrySpan | undefined ;
247
- spanContext : SentrySpanArguments ;
274
+ spanArguments : SentrySpanArguments ;
248
275
forceTransaction ?: boolean ;
249
276
scope : Scope ;
250
277
} ) : Span {
@@ -256,7 +283,7 @@ function createChildOrRootSpan({
256
283
257
284
let span : Span ;
258
285
if ( parentSpan && ! forceTransaction ) {
259
- span = _startChildSpan ( parentSpan , scope , spanContext ) ;
286
+ span = _startChildSpan ( parentSpan , scope , spanArguments ) ;
260
287
addChildSpanToSpan ( parentSpan , span ) ;
261
288
} else if ( parentSpan ) {
262
289
// 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({
268
295
{
269
296
traceId,
270
297
parentSpanId,
271
- ...spanContext ,
298
+ ...spanArguments ,
272
299
} ,
273
300
scope ,
274
301
parentSampled ,
@@ -290,7 +317,7 @@ function createChildOrRootSpan({
290
317
{
291
318
traceId,
292
319
parentSpanId,
293
- ...spanContext ,
320
+ ...spanArguments ,
294
321
} ,
295
322
scope ,
296
323
parentSampled ,
@@ -312,19 +339,17 @@ function createChildOrRootSpan({
312
339
* This converts StartSpanOptions to SentrySpanArguments.
313
340
* For the most part (for now) we accept the same options,
314
341
* but some of them need to be transformed.
315
- *
316
- * Eventually the StartSpanOptions will be more aligned with OpenTelemetry.
317
342
*/
318
- function normalizeContext ( context : StartSpanOptions ) : SentrySpanArguments {
319
- const exp = context . experimental || { } ;
343
+ function parseSentrySpanArguments ( options : StartSpanOptions ) : SentrySpanArguments {
344
+ const exp = options . experimental || { } ;
320
345
const initialCtx : SentrySpanArguments = {
321
346
isStandalone : exp . standalone ,
322
- ...context ,
347
+ ...options ,
323
348
} ;
324
349
325
- if ( context . startTime ) {
350
+ if ( options . startTime ) {
326
351
const ctx : SentrySpanArguments & { startTime ?: SpanTimeInput } = { ...initialCtx } ;
327
- ctx . startTimestamp = spanTimeInputToSeconds ( context . startTime ) ;
352
+ ctx . startTimestamp = spanTimeInputToSeconds ( options . startTime ) ;
328
353
delete ctx . startTime ;
329
354
return ctx ;
330
355
}
@@ -419,3 +444,11 @@ function getParentSpan(scope: Scope): SentrySpan | undefined {
419
444
420
445
return span ;
421
446
}
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