Skip to content

Commit 410ca15

Browse files
committed
feat(browser): Add setSpanActive to create an active root span in the browser
1 parent d7538cd commit 410ca15

File tree

15 files changed

+286
-1
lines changed

15 files changed

+286
-1
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
tracesSampleRate: 1,
8+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
2+
Sentry.setSpanActive(checkoutSpan);
3+
4+
Sentry.startSpan({ name: 'checkout-step-1' }, () => {
5+
Sentry.startSpan({ name: 'checkout-step-1-1' }, () => {
6+
// ... `
7+
});
8+
});
9+
10+
Sentry.startSpan({ name: 'checkout-step-2' }, () => {
11+
// ... `
12+
});
13+
14+
checkoutSpan.end();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
4+
5+
sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => {
6+
if (shouldSkipTracingTest()) {
7+
sentryTest.skip();
8+
}
9+
10+
const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow');
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
await page.goto(url);
14+
15+
const checkoutEvent = envelopeRequestParser(await req);
16+
const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id;
17+
expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/);
18+
19+
expect(checkoutEvent.spans).toHaveLength(3);
20+
21+
const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1');
22+
const checkoutStep11 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1-1');
23+
const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2');
24+
25+
expect(checkoutStep1).toBeDefined();
26+
expect(checkoutStep11).toBeDefined();
27+
expect(checkoutStep2).toBeDefined();
28+
29+
expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId);
30+
expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId);
31+
32+
// despite 1-1 being called within 1, it's still parented to the root span
33+
// due to this being default behaviour in browser environments
34+
expect(checkoutStep11?.parent_span_id).toBe(checkoutSpanId);
35+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
tracesSampleRate: 1,
8+
parentSpanIsAlwaysRootSpan: false,
9+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
2+
Sentry.setSpanActive(checkoutSpan);
3+
4+
Sentry.startSpan({ name: 'checkout-step-1' }, () => {});
5+
6+
const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' });
7+
Sentry.setSpanActive(checkoutStep2);
8+
9+
Sentry.startSpan({ name: 'checkout-step-2-1' }, () => {
10+
// ... `
11+
});
12+
checkoutStep2.end();
13+
14+
Sentry.startSpan({ name: 'checkout-step-3' }, () => {});
15+
16+
checkoutSpan.end();
17+
18+
Sentry.startSpan({ name: 'post-checkout' }, () => {
19+
Sentry.startSpan({ name: 'post-checkout-1' }, () => {
20+
// ... `
21+
});
22+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
4+
5+
sentryTest(
6+
'nested calls to setSpanActive with parentSpanIsAlwaysRootSpan=false result in correct parenting',
7+
async ({ getLocalTestUrl, page }) => {
8+
if (shouldSkipTracingTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow');
13+
const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout');
14+
15+
const url = await getLocalTestUrl({ testDir: __dirname });
16+
await page.goto(url);
17+
18+
const checkoutEvent = envelopeRequestParser(await req);
19+
const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq);
20+
21+
const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id;
22+
const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id;
23+
24+
expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/);
25+
expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/);
26+
27+
expect(checkoutEvent.spans).toHaveLength(4);
28+
expect(postCheckoutEvent.spans).toHaveLength(1);
29+
30+
const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1');
31+
const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2');
32+
const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1');
33+
const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3');
34+
35+
expect(checkoutStep1).toBeDefined();
36+
expect(checkoutStep2).toBeDefined();
37+
expect(checkoutStep21).toBeDefined();
38+
expect(checkoutStep3).toBeDefined();
39+
40+
expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId);
41+
expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId);
42+
43+
// with parentSpanIsAlwaysRootSpan=false, 2-1 is parented to 2 because
44+
// 2 was the active span when 2-1 was started
45+
expect(checkoutStep21?.parent_span_id).toBe(checkoutStep2?.span_id);
46+
47+
// since the parent of three is `checkoutSpan`, we correctly reset
48+
// the active span to `checkoutSpan` after 2 ended
49+
expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId);
50+
51+
// post-checkout trace is started as a new trace because ending checkoutSpan removes the active
52+
// span on the scope
53+
const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1');
54+
expect(postCheckoutStep1).toBeDefined();
55+
expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId);
56+
},
57+
);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
tracesSampleRate: 1,
8+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
2+
Sentry.setSpanActive(checkoutSpan);
3+
4+
Sentry.startSpan({ name: 'checkout-step-1' }, () => {});
5+
6+
const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' });
7+
Sentry.setSpanActive(checkoutStep2);
8+
9+
Sentry.startSpan({ name: 'checkout-step-2-1' }, () => {
10+
// ... `
11+
});
12+
checkoutStep2.end();
13+
14+
Sentry.startSpan({ name: 'checkout-step-3' }, () => {});
15+
16+
checkoutSpan.end();
17+
18+
Sentry.startSpan({ name: 'post-checkout' }, () => {
19+
Sentry.startSpan({ name: 'post-checkout-1' }, () => {
20+
// ... `
21+
});
22+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
4+
5+
sentryTest('nested calls to setSpanActive still parent to root span by default', async ({ getLocalTestUrl, page }) => {
6+
if (shouldSkipTracingTest()) {
7+
sentryTest.skip();
8+
}
9+
10+
const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow');
11+
const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout');
12+
13+
const url = await getLocalTestUrl({ testDir: __dirname });
14+
await page.goto(url);
15+
16+
const checkoutEvent = envelopeRequestParser(await req);
17+
const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq);
18+
19+
const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id;
20+
const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id;
21+
22+
expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/);
23+
expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/);
24+
25+
expect(checkoutEvent.spans).toHaveLength(4);
26+
expect(postCheckoutEvent.spans).toHaveLength(1);
27+
28+
const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1');
29+
const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2');
30+
const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1');
31+
const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3');
32+
33+
expect(checkoutStep1).toBeDefined();
34+
expect(checkoutStep2).toBeDefined();
35+
expect(checkoutStep21).toBeDefined();
36+
expect(checkoutStep3).toBeDefined();
37+
38+
expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId);
39+
expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId);
40+
expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId);
41+
42+
// despite 2-1 being called within 2 AND setting 2 as active span, it's still parented to the
43+
// root span due to this being default behaviour in browser environments
44+
expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId);
45+
46+
const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1');
47+
expect(postCheckoutStep1).toBeDefined();
48+
expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId);
49+
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export {
2222
startBrowserTracingNavigationSpan,
2323
startBrowserTracingPageLoadSpan,
2424
} from './tracing/browserTracingIntegration';
25+
export { setSpanActive } from './tracing/setSpanActive';
2526

2627
export { reportPageLoaded } from './tracing/reportPageLoaded';
2728

0 commit comments

Comments
 (0)