Skip to content

Commit 330750d

Browse files
authored
feat(core): Add startNewTrace API (#12138)
Add a new `Sentry.startNewTrace` function that allows users to start a trace in isolation of a potentially still active trace. When this function is called, a new trace will be started on a forked scope which remains valid throughout the callback lifetime.
1 parent 5a1fae6 commit 330750d

File tree

24 files changed

+227
-18
lines changed

24 files changed

+227
-18
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const newTraceBtn = document.getElementById('newTrace');
2+
newTraceBtn.addEventListener('click', async () => {
3+
Sentry.startNewTrace(() => {
4+
Sentry.startSpan({ op: 'ui.interaction.click', name: 'new-trace' }, async () => {
5+
await fetch('http://example.com');
6+
});
7+
});
8+
});
9+
10+
const oldTraceBtn = document.getElementById('oldTrace');
11+
oldTraceBtn.addEventListener('click', async () => {
12+
Sentry.startSpan({ op: 'ui.interaction.click', name: 'old-trace' }, async () => {
13+
await fetch('http://example.com');
14+
});
15+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="oldTrace">Old Trace</button>
8+
<button id="newTrace">new Trace</button>
9+
</body>
10+
</html>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import type { EventAndTraceHeader } from '../../../../utils/helpers';
4+
import {
5+
eventAndTraceHeaderRequestParser,
6+
getFirstSentryEnvelopeRequest,
7+
getMultipleSentryEnvelopeRequests,
8+
shouldSkipTracingTest,
9+
} from '../../../../utils/helpers';
10+
11+
sentryTest(
12+
'creates a new trace if `startNewTrace` is called and leaves old trace valid outside the callback',
13+
async ({ getLocalTestUrl, page }) => {
14+
if (shouldSkipTracingTest()) {
15+
sentryTest.skip();
16+
}
17+
18+
const url = await getLocalTestUrl({ testDir: __dirname });
19+
20+
await page.route('http://example.com/**', route => {
21+
return route.fulfill({
22+
status: 200,
23+
contentType: 'application/json',
24+
body: JSON.stringify({}),
25+
});
26+
});
27+
28+
const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest<EventAndTraceHeader>(
29+
page,
30+
url,
31+
eventAndTraceHeaderRequestParser,
32+
);
33+
34+
const pageloadTraceContext = pageloadEvent.contexts?.trace;
35+
36+
expect(pageloadEvent.type).toEqual('transaction');
37+
38+
expect(pageloadTraceContext).toMatchObject({
39+
op: 'pageload',
40+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
41+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
42+
});
43+
expect(pageloadTraceContext).not.toHaveProperty('parent_span_id');
44+
45+
expect(pageloadTraceHeaders).toEqual({
46+
environment: 'production',
47+
public_key: 'public',
48+
sample_rate: '1',
49+
sampled: 'true',
50+
trace_id: pageloadTraceContext?.trace_id,
51+
});
52+
53+
const transactionPromises = getMultipleSentryEnvelopeRequests<EventAndTraceHeader>(
54+
page,
55+
2,
56+
{ envelopeType: 'transaction' },
57+
eventAndTraceHeaderRequestParser,
58+
);
59+
60+
await page.locator('#newTrace').click();
61+
await page.locator('#oldTrace').click();
62+
63+
const [txnEvent1, txnEvent2] = await transactionPromises;
64+
65+
const [newTraceTransactionEvent, newTraceTransactionTraceHeaders] =
66+
txnEvent1[0].transaction === 'new-trace' ? txnEvent1 : txnEvent2;
67+
const [oldTraceTransactionEvent, oldTraceTransactionTraceHeaders] =
68+
txnEvent1[0].transaction === 'old-trace' ? txnEvent1 : txnEvent2;
69+
70+
const newTraceTransactionTraceContext = newTraceTransactionEvent.contexts?.trace;
71+
expect(newTraceTransactionTraceContext).toMatchObject({
72+
op: 'ui.interaction.click',
73+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
74+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
75+
});
76+
77+
expect(newTraceTransactionTraceHeaders).toEqual({
78+
environment: 'production',
79+
public_key: 'public',
80+
sample_rate: '1',
81+
sampled: 'true',
82+
trace_id: newTraceTransactionTraceContext?.trace_id,
83+
transaction: 'new-trace',
84+
});
85+
86+
const oldTraceTransactionEventTraceContext = oldTraceTransactionEvent.contexts?.trace;
87+
expect(oldTraceTransactionEventTraceContext).toMatchObject({
88+
op: 'ui.interaction.click',
89+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
90+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
91+
});
92+
93+
expect(oldTraceTransactionTraceHeaders).toEqual({
94+
environment: 'production',
95+
public_key: 'public',
96+
sample_rate: '1',
97+
sampled: 'true',
98+
trace_id: oldTraceTransactionTraceHeaders?.trace_id,
99+
// transaction: 'old-trace', <-- this is not in the DSC because the DSC is continued from the pageload transaction
100+
// which does not have a `transaction` field because its source is URL.
101+
});
102+
103+
expect(oldTraceTransactionEventTraceContext?.trace_id).toEqual(pageloadTraceContext?.trace_id);
104+
expect(newTraceTransactionTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id);
105+
},
106+
);

packages/astro/src/index.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export {
6464
startSpan,
6565
startInactiveSpan,
6666
startSpanManual,
67+
startNewTrace,
6768
withActiveSpan,
6869
getSpanDescendants,
6970
continueTrace,

packages/aws-serverless/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export {
6262
startSpan,
6363
startInactiveSpan,
6464
startSpanManual,
65+
startNewTrace,
6566
withActiveSpan,
6667
getRootSpan,
6768
getSpanDescendants,

packages/browser/src/index.bundle.tracing.replay.feedback.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
startSpan,
1111
startInactiveSpan,
1212
startSpanManual,
13+
startNewTrace,
1314
withActiveSpan,
1415
getSpanDescendants,
1516
setMeasurement,

packages/browser/src/index.bundle.tracing.replay.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export {
1010
startSpan,
1111
startInactiveSpan,
1212
startSpanManual,
13+
startNewTrace,
1314
withActiveSpan,
1415
getSpanDescendants,
1516
setMeasurement,

packages/browser/src/index.bundle.tracing.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
startSpan,
1212
startInactiveSpan,
1313
startSpanManual,
14+
startNewTrace,
1415
withActiveSpan,
1516
getSpanDescendants,
1617
setMeasurement,

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export {
5959
startInactiveSpan,
6060
startSpanManual,
6161
withActiveSpan,
62+
startNewTrace,
6263
getSpanDescendants,
6364
setMeasurement,
6465
getSpanStatusFromHttpCode,

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import type { Client, IntegrationFn, StartSpanOptions, TransactionSource } from
2727
import type { Span } from '@sentry/types';
2828
import {
2929
browserPerformanceTimeOrigin,
30+
generatePropagationContext,
3031
getDomElement,
3132
logger,
3233
propagationContextFromHeaders,
33-
uuid4,
3434
} from '@sentry/utils';
3535

3636
import { DEBUG_BUILD } from '../debug-build';
@@ -412,8 +412,8 @@ export function startBrowserTracingPageLoadSpan(
412412
* This will only do something if a browser tracing integration has been setup.
413413
*/
414414
export function startBrowserTracingNavigationSpan(client: Client, spanOptions: StartSpanOptions): Span | undefined {
415-
getCurrentScope().setPropagationContext(generatePropagationContext());
416415
getIsolationScope().setPropagationContext(generatePropagationContext());
416+
getCurrentScope().setPropagationContext(generatePropagationContext());
417417

418418
client.emit('startNavigationSpan', spanOptions);
419419

@@ -487,10 +487,3 @@ function registerInteractionListener(
487487
addEventListener('click', registerInteractionTransaction, { once: false, capture: true });
488488
}
489489
}
490-
491-
function generatePropagationContext(): { traceId: string; spanId: string } {
492-
return {
493-
traceId: uuid4(),
494-
spanId: uuid4().substring(16),
495-
};
496-
}

0 commit comments

Comments
 (0)