Skip to content

Commit 3c048c3

Browse files
authored
feat(browser): Add support for propagateTraceparent SDK option (#17509)
This PR adds support for a new browser SDK init option, `propagateTraceparent`, as [spec'd out in our develop docs](https://develop.sentry.dev/sdk/telemetry/traces/#propagatetraceparent) If users opt into `propagateTraceparent`, browser SDKs will attach a W3C compliant `traceparent` header to outgoing fetch and XHR requests, in addition to `sentry-trace` and `baggage` headers.
1 parent 47d213b commit 3c048c3

File tree

46 files changed

+687
-55
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+687
-55
lines changed

.size-limit.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ module.exports = [
7575
path: 'packages/browser/build/npm/esm/index.js',
7676
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'),
7777
gzip: true,
78-
limit: '83 KB',
78+
limit: '84 KB',
7979
},
8080
{
8181
name: '@sentry/browser (incl. Tracing, Replay, Feedback)',
@@ -120,7 +120,7 @@ module.exports = [
120120
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
121121
ignore: ['react/jsx-runtime'],
122122
gzip: true,
123-
limit: '42 KB',
123+
limit: '43 KB',
124124
},
125125
// Vue SDK (ESM)
126126
{
@@ -206,7 +206,7 @@ module.exports = [
206206
import: createImport('init'),
207207
ignore: ['next/router', 'next/constants'],
208208
gzip: true,
209-
limit: '44 KB',
209+
limit: '45 KB',
210210
},
211211
// SvelteKit SDK (ESM)
212212
{

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/init.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ Sentry.init({
66
dsn: 'https://[email protected]/1337',
77
integrations: [Sentry.browserTracingIntegration()],
88
tracesSampleRate: 1,
9+
propagateTraceparent: true,
910
});

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { sentryTest } from '../../../../../utils/fixtures';
33
import { shouldSkipTracingTest } from '../../../../../utils/helpers';
44

55
sentryTest(
6-
'should not attach `sentry-trace` and `baggage` header to cross-origin requests when no tracePropagationTargets are defined',
6+
"doesn't attach `sentry-trace` and `baggage` or `traceparent` (if `propagateTraceparent` is true) header to cross-origin requests when no tracePropagationTargets are defined",
77
async ({ getLocalTestUrl, page }) => {
88
if (shouldSkipTracingTest()) {
99
sentryTest.skip();
@@ -25,6 +25,7 @@ sentryTest(
2525
expect(requestHeaders).not.toMatchObject({
2626
'sentry-trace': expect.any(String),
2727
baggage: expect.any(String),
28+
traceparent: expect.any(String),
2829
});
2930
}
3031
},
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.browserTracingIntegration()],
8+
tracePropagationTargets: ['http://sentry-test-site.example'],
9+
tracesSampleRate: 1,
10+
propagateTraceparent: true,
11+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fetch('http://sentry-test-site.example/0').then(
2+
fetch('http://sentry-test-site.example/1', { headers: { 'X-Test-Header': 'existing-header' } }).then(
3+
fetch('http://sentry-test-site.example/2'),
4+
),
5+
);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect } from '@playwright/test';
2+
import { extractTraceparentData } from '@sentry/core';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { shouldSkipTracingTest } from '../../../../utils/helpers';
5+
6+
sentryTest(
7+
'attaches traceparent header to fetch requests if `propagateTraceparent` is true',
8+
async ({ getLocalTestUrl, page }) => {
9+
if (shouldSkipTracingTest()) {
10+
sentryTest.skip();
11+
}
12+
13+
await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' }));
14+
15+
const url = await getLocalTestUrl({ testDir: __dirname });
16+
17+
const requests = (
18+
await Promise.all([
19+
page.goto(url),
20+
Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://sentry-test-site.example/${idx}`))),
21+
])
22+
)[1];
23+
24+
expect(requests).toHaveLength(3);
25+
26+
const request1 = requests[0];
27+
const requestHeaders1 = request1.headers();
28+
const traceparentData1 = extractTraceparentData(requestHeaders1['sentry-trace']);
29+
expect(traceparentData1).toMatchObject({
30+
traceId: expect.stringMatching(/^([a-f0-9]{32})$/),
31+
parentSpanId: expect.stringMatching(/^([a-f0-9]{16})$/),
32+
parentSampled: true,
33+
});
34+
35+
expect(requestHeaders1).toMatchObject({
36+
'sentry-trace': `${traceparentData1?.traceId}-${traceparentData1?.parentSpanId}-1`,
37+
baggage: expect.stringContaining(`sentry-trace_id=${traceparentData1?.traceId}`),
38+
traceparent: `00-${traceparentData1?.traceId}-${traceparentData1?.parentSpanId}-01`,
39+
});
40+
41+
const request2 = requests[1];
42+
const requestHeaders2 = request2.headers();
43+
const traceparentData2 = extractTraceparentData(requestHeaders2['sentry-trace']);
44+
expect(requestHeaders2).toMatchObject({
45+
'sentry-trace': `${traceparentData2?.traceId}-${traceparentData2?.parentSpanId}-1`,
46+
baggage: expect.stringContaining(`sentry-trace_id=${traceparentData2?.traceId}`),
47+
traceparent: `00-${traceparentData2?.traceId}-${traceparentData2?.parentSpanId}-01`,
48+
'x-test-header': 'existing-header',
49+
});
50+
51+
const request3 = requests[2];
52+
const requestHeaders3 = request3.headers();
53+
const traceparentData3 = extractTraceparentData(requestHeaders3['sentry-trace']);
54+
expect(requestHeaders3).toMatchObject({
55+
'sentry-trace': `${traceparentData3?.traceId}-${traceparentData3?.parentSpanId}-1`,
56+
baggage: expect.stringContaining(`sentry-trace_id=${traceparentData3?.traceId}`),
57+
traceparent: `00-${traceparentData3?.traceId}-${traceparentData3?.parentSpanId}-01`,
58+
});
59+
},
60+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.browserTracingIntegration()],
8+
tracePropagationTargets: ['http://sentry-test-site.example'],
9+
tracesSampleRate: 0,
10+
propagateTraceparent: true,
11+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fetch('http://sentry-test-site.example/0');
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { expect } from '@playwright/test';
2+
import { extractTraceparentData } from '@sentry/core';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { shouldSkipTracingTest } from '../../../../utils/helpers';
5+
6+
sentryTest(
7+
'attaches traceparent header to unsampled fetch requests if `propagateTraceparent` is true',
8+
async ({ getLocalTestUrl, page }) => {
9+
if (shouldSkipTracingTest()) {
10+
sentryTest.skip();
11+
}
12+
13+
const url = await getLocalTestUrl({ testDir: __dirname });
14+
15+
const [, request] = await Promise.all([page.goto(url), page.waitForRequest('http://sentry-test-site.example/0')]);
16+
17+
const requestHeaders = request.headers();
18+
19+
const traceparentData = extractTraceparentData(requestHeaders['sentry-trace']);
20+
expect(traceparentData).toMatchObject({
21+
traceId: expect.stringMatching(/^([a-f0-9]{32})$/),
22+
parentSpanId: expect.stringMatching(/^([a-f0-9]{16})$/),
23+
parentSampled: false,
24+
});
25+
26+
expect(requestHeaders).toMatchObject({
27+
'sentry-trace': `${traceparentData?.traceId}-${traceparentData?.parentSpanId}-0`,
28+
traceparent: `00-${traceparentData?.traceId}-${traceparentData?.parentSpanId}-00`,
29+
baggage: expect.stringContaining(`sentry-trace_id=${traceparentData?.traceId}`),
30+
});
31+
},
32+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.browserTracingIntegration()],
8+
tracePropagationTargets: ['http://sentry-test-site.example'],
9+
// no tracesSampleRate defined means TWP mode
10+
propagateTraceparent: true,
11+
});

0 commit comments

Comments
 (0)