-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathtrace.ts
More file actions
447 lines (399 loc) · 14.8 KB
/
trace.ts
File metadata and controls
447 lines (399 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
import type { Scope, Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types';
import { addNonEnumerableProperty, dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils';
import { getDynamicSamplingContextFromSpan } from '.';
import { DEBUG_BUILD } from '../debug-build';
import { getCurrentScope, withScope } from '../exports';
import type { Hub } from '../hub';
import { runWithAsyncContext } from '../hub';
import { getIsolationScope } from '../hub';
import { getCurrentHub } from '../hub';
import type { Scope as ScopeClass } from '../scope';
import { handleCallbackErrors } from '../utils/handleCallbackErrors';
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
*
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
* or you didn't set `tracesSampleRate`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*
* This function is meant to be used internally and may break at any time. Use at your own risk.
*
* @internal
* @private
*
* @deprecated Use `startSpan` instead.
*/
export function trace<T>(
context: TransactionContext,
callback: (span?: Span) => T,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onError: (error: unknown, span?: Span) => void = () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
afterFinish: () => void = () => {},
): T {
// eslint-disable-next-line deprecation/deprecation
const hub = getCurrentHub();
const scope = getCurrentScope();
// eslint-disable-next-line deprecation/deprecation
const parentSpan = scope.getSpan();
const spanContext = normalizeContext(context);
const activeSpan = createChildSpanOrTransaction(hub, {
parentSpan,
spanContext,
forceTransaction: false,
scope,
});
// eslint-disable-next-line deprecation/deprecation
scope.setSpan(activeSpan);
return handleCallbackErrors(
() => callback(activeSpan),
error => {
activeSpan && activeSpan.setStatus('internal_error');
onError(error, activeSpan);
},
() => {
activeSpan && activeSpan.end();
// eslint-disable-next-line deprecation/deprecation
scope.setSpan(parentSpan);
afterFinish();
},
);
}
/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getSpan()`, as long as the function is executed while the scope is active.
*
* If you want to create a span that is not set as active, use {@link startInactiveSpan}.
*
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
* or you didn't set `tracesSampleRate`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
export function startSpan<T>(context: StartSpanOptions, callback: (span: Span | undefined) => T): T {
const spanContext = normalizeContext(context);
return runWithAsyncContext(() => {
return withScope(context.scope, scope => {
// eslint-disable-next-line deprecation/deprecation
const hub = getCurrentHub();
// eslint-disable-next-line deprecation/deprecation
const parentSpan = scope.getSpan();
const shouldSkipSpan = context.onlyIfParent && !parentSpan;
const activeSpan = shouldSkipSpan
? undefined
: createChildSpanOrTransaction(hub, {
parentSpan,
spanContext,
forceTransaction: context.forceTransaction,
scope,
});
return handleCallbackErrors(
() => callback(activeSpan),
() => {
// Only update the span status if it hasn't been changed yet
if (activeSpan) {
const { status } = spanToJSON(activeSpan);
if (!status || status === 'ok') {
activeSpan.setStatus('internal_error');
}
}
},
() => activeSpan && activeSpan.end(),
);
});
});
}
/**
* @deprecated Use {@link startSpan} instead.
*/
export const startActiveSpan = startSpan;
/**
* Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span
* after the function is done automatically. You'll have to call `span.end()` manually.
*
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
*
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
* or you didn't set `tracesSampleRate`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
export function startSpanManual<T>(
context: StartSpanOptions,
callback: (span: Span | undefined, finish: () => void) => T,
): T {
const spanContext = normalizeContext(context);
return runWithAsyncContext(() => {
return withScope(context.scope, scope => {
// eslint-disable-next-line deprecation/deprecation
const hub = getCurrentHub();
// eslint-disable-next-line deprecation/deprecation
const parentSpan = scope.getSpan();
const shouldSkipSpan = context.onlyIfParent && !parentSpan;
const activeSpan = shouldSkipSpan
? undefined
: createChildSpanOrTransaction(hub, {
parentSpan,
spanContext,
forceTransaction: context.forceTransaction,
scope,
});
function finishAndSetSpan(): void {
activeSpan && activeSpan.end();
}
return handleCallbackErrors(
() => callback(activeSpan, finishAndSetSpan),
() => {
// Only update the span status if it hasn't been changed yet, and the span is not yet finished
if (activeSpan && activeSpan.isRecording()) {
const { status } = spanToJSON(activeSpan);
if (!status || status === 'ok') {
activeSpan.setStatus('internal_error');
}
}
},
);
});
});
}
/**
* Creates a span. This span is not set as active, so will not get automatic instrumentation spans
* as children or be able to be accessed via `Sentry.getSpan()`.
*
* If you want to create a span that is set as active, use {@link startSpan}.
*
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
* or you didn't set `tracesSampleRate` or `tracesSampler`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
export function startInactiveSpan(context: StartSpanOptions): Span | undefined {
if (!hasTracingEnabled()) {
return undefined;
}
const spanContext = normalizeContext(context);
// eslint-disable-next-line deprecation/deprecation
const hub = getCurrentHub();
const parentSpan = context.scope
? // eslint-disable-next-line deprecation/deprecation
context.scope.getSpan()
: getActiveSpan();
const shouldSkipSpan = context.onlyIfParent && !parentSpan;
if (shouldSkipSpan) {
return undefined;
}
const scope = context.scope || getCurrentScope();
// Even though we don't actually want to make this span active on the current scope,
// we need to make it active on a temporary scope that we use for event processing
// as otherwise, it won't pick the correct span for the event when processing it
const temporaryScope = (scope as ScopeClass).clone();
return createChildSpanOrTransaction(hub, {
parentSpan,
spanContext,
forceTransaction: context.forceTransaction,
scope: temporaryScope,
});
}
/**
* Returns the currently active span.
*/
export function getActiveSpan(): Span | undefined {
// eslint-disable-next-line deprecation/deprecation
return getCurrentScope().getSpan();
}
interface ContinueTrace {
/**
* Continue a trace from `sentry-trace` and `baggage` values.
* These values can be obtained from incoming request headers,
* or in the browser from `<meta name="sentry-trace">` and `<meta name="baggage">` HTML tags.
*
* @deprecated Use the version of this function taking a callback as second parameter instead:
*
* ```
* Sentry.continueTrace(sentryTrace: '...', baggage: '...' }, () => {
* // ...
* })
* ```
*
*/
({
sentryTrace,
baggage,
}: {
// eslint-disable-next-line deprecation/deprecation
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
// eslint-disable-next-line deprecation/deprecation
baggage: Parameters<typeof tracingContextFromHeaders>[1];
}): Partial<TransactionContext>;
/**
* Continue a trace from `sentry-trace` and `baggage` values.
* These values can be obtained from incoming request headers, or in the browser from `<meta name="sentry-trace">`
* and `<meta name="baggage">` HTML tags.
*
* Spans started with `startSpan`, `startSpanManual` and `startInactiveSpan`, within the callback will automatically
* be attached to the incoming trace.
*
* Deprecation notice: In the next major version of the SDK the provided callback will not receive a transaction
* context argument.
*/
<V>(
{
sentryTrace,
baggage,
}: {
// eslint-disable-next-line deprecation/deprecation
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
// eslint-disable-next-line deprecation/deprecation
baggage: Parameters<typeof tracingContextFromHeaders>[1];
},
// TODO(v8): Remove parameter from this callback.
callback: (transactionContext: Partial<TransactionContext>) => V,
): V;
}
export const continueTrace: ContinueTrace = <V>(
{
sentryTrace,
baggage,
}: {
// eslint-disable-next-line deprecation/deprecation
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
// eslint-disable-next-line deprecation/deprecation
baggage: Parameters<typeof tracingContextFromHeaders>[1];
},
callback?: (transactionContext: Partial<TransactionContext>) => V,
): V | Partial<TransactionContext> => {
// TODO(v8): Change this function so it doesn't do anything besides setting the propagation context on the current scope:
/*
return withScope((scope) => {
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
scope.setPropagationContext(propagationContext);
return callback();
})
*/
const currentScope = getCurrentScope();
// eslint-disable-next-line deprecation/deprecation
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
sentryTrace,
baggage,
);
currentScope.setPropagationContext(propagationContext);
if (DEBUG_BUILD && traceparentData) {
logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
}
const transactionContext: Partial<TransactionContext> = {
...traceparentData,
metadata: dropUndefinedKeys({
dynamicSamplingContext,
}),
};
if (!callback) {
return transactionContext;
}
return runWithAsyncContext(() => {
return callback(transactionContext);
});
};
function createChildSpanOrTransaction(
// eslint-disable-next-line deprecation/deprecation
hub: Hub,
{
parentSpan,
spanContext,
forceTransaction,
scope,
}: {
parentSpan: Span | undefined;
spanContext: TransactionContext;
forceTransaction?: boolean;
scope: Scope;
},
): Span | undefined {
if (!hasTracingEnabled()) {
return undefined;
}
const isolationScope = getIsolationScope();
let span: Span | undefined;
if (parentSpan && !forceTransaction) {
// eslint-disable-next-line deprecation/deprecation
span = parentSpan.startChild(spanContext);
} else if (parentSpan) {
// If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
const dsc = getDynamicSamplingContextFromSpan(parentSpan);
const { traceId, spanId: parentSpanId } = parentSpan.spanContext();
const sampled = spanIsSampled(parentSpan);
// eslint-disable-next-line deprecation/deprecation
span = hub.startTransaction({
traceId,
parentSpanId,
parentSampled: sampled,
...spanContext,
metadata: {
dynamicSamplingContext: dsc,
// eslint-disable-next-line deprecation/deprecation
...spanContext.metadata,
},
});
} else {
const { traceId, dsc, parentSpanId, sampled } = {
...isolationScope.getPropagationContext(),
...scope.getPropagationContext(),
};
// eslint-disable-next-line deprecation/deprecation
span = hub.startTransaction({
traceId,
parentSpanId,
parentSampled: sampled,
...spanContext,
metadata: {
dynamicSamplingContext: dsc,
// eslint-disable-next-line deprecation/deprecation
...spanContext.metadata,
},
});
}
// We always set this as active span on the scope
// In the case of this being an inactive span, we ensure to pass a detached scope in here in the first place
// But by having this here, we can ensure that the lookup through `getCapturedScopesOnSpan` results in the correct scope & span combo
// eslint-disable-next-line deprecation/deprecation
scope.setSpan(span);
setCapturedScopesOnSpan(span, scope, isolationScope);
return span;
}
/**
* This converts StartSpanOptions to TransactionContext.
* For the most part (for now) we accept the same options,
* but some of them need to be transformed.
*
* Eventually the StartSpanOptions will be more aligned with OpenTelemetry.
*/
function normalizeContext(context: StartSpanOptions): TransactionContext {
if (context.startTime) {
const ctx: TransactionContext & { startTime?: SpanTimeInput } = { ...context };
ctx.startTimestamp = spanTimeInputToSeconds(context.startTime);
delete ctx.startTime;
return ctx;
}
return context;
}
const SCOPE_ON_START_SPAN_FIELD = '_sentryScope';
const ISOLATION_SCOPE_ON_START_SPAN_FIELD = '_sentryIsolationScope';
type SpanWithScopes = Span & {
[SCOPE_ON_START_SPAN_FIELD]?: Scope;
[ISOLATION_SCOPE_ON_START_SPAN_FIELD]?: Scope;
};
function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void {
if (span) {
addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, isolationScope);
addNonEnumerableProperty(span, SCOPE_ON_START_SPAN_FIELD, scope);
}
}
/**
* Grabs the scope and isolation scope off a span that were active when the span was started.
*/
export function getCapturedScopesOnSpan(span: Span): { scope?: Scope; isolationScope?: Scope } {
return {
scope: (span as SpanWithScopes)[SCOPE_ON_START_SPAN_FIELD],
isolationScope: (span as SpanWithScopes)[ISOLATION_SCOPE_ON_START_SPAN_FIELD],
};
}