Skip to content

Commit 8227554

Browse files
committed
fix(core): Handle manually set in sentry tracing headers in fetch calls
1 parent 7c50cd4 commit 8227554

File tree

5 files changed

+497
-59
lines changed

5 files changed

+497
-59
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
integrations: [Sentry.browserTracingIntegration()],
8+
tracePropagationTargets: ['http://sentry-test-site.example'],
9+
tracesSampleRate: 1,
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fetch('http://sentry-test-site.example/api/test/', {
2+
headers: { 'sentry-trace': 'abc-123-1', baggage: 'sentry-trace_id=abc' },
3+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import { shouldSkipTracingTest } from '../../../../utils/helpers';
4+
5+
sentryTest("instrumentation doesn't override manually added sentry headers", async ({ getLocalTestUrl, page }) => {
6+
if (shouldSkipTracingTest()) {
7+
sentryTest.skip();
8+
}
9+
10+
const requestPromise = page.waitForRequest('http://sentry-test-site.example/api/test/');
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
14+
await page.goto(url);
15+
16+
const request = await requestPromise;
17+
18+
const headers = await request.allHeaders();
19+
20+
expect(headers['sentry-trace']).toBe('abc-123-1');
21+
expect(headers.baggage).toBe('sentry-trace_id=abc');
22+
});

packages/core/src/fetch.ts

+53-59
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ export function instrumentFetchRequest(
103103

104104
/**
105105
* Adds sentry-trace and baggage headers to the various forms of fetch headers.
106+
* exported only for testing purposes
106107
*/
107-
function _addTracingHeadersToFetchRequest(
108+
// eslint-disable-next-line complexity -- yup it's this complicated :(
109+
export function _addTracingHeadersToFetchRequest(
108110
request: string | Request,
109111
fetchOptionsObj: {
110112
headers?:
@@ -124,78 +126,74 @@ function _addTracingHeadersToFetchRequest(
124126
return undefined;
125127
}
126128

127-
const headers = fetchOptionsObj.headers || (isRequest(request) ? request.headers : undefined);
129+
const originalHeaders = fetchOptionsObj.headers || (isRequest(request) ? request.headers : undefined);
128130

129-
if (!headers) {
131+
if (!originalHeaders) {
130132
return { ...traceHeaders };
131-
} else if (isHeaders(headers)) {
132-
const newHeaders = new Headers(headers);
133-
newHeaders.set('sentry-trace', sentryTrace);
133+
} else if (isHeaders(originalHeaders)) {
134+
const newHeaders = new Headers(originalHeaders);
135+
136+
// We don't want to override manually added sentry headers
137+
if (!newHeaders.get('sentry-trace')) {
138+
newHeaders.set('sentry-trace', sentryTrace);
139+
}
134140

135141
if (baggage) {
136142
const prevBaggageHeader = newHeaders.get('baggage');
137-
if (prevBaggageHeader) {
138-
const prevHeaderStrippedFromSentryBaggage = stripBaggageHeaderOfSentryBaggageValues(prevBaggageHeader);
139-
newHeaders.set(
140-
'baggage',
141-
// If there are non-sentry entries (i.e. if the stripped string is non-empty/truthy) combine the stripped header and sentry baggage header
142-
// otherwise just set the sentry baggage header
143-
prevHeaderStrippedFromSentryBaggage ? `${prevHeaderStrippedFromSentryBaggage},${baggage}` : baggage,
144-
);
145-
} else {
143+
144+
// 3 cases:
145+
// 1. No previous baggage header -> add baggage
146+
// 2. Previous baggage header has no sentry baggage values -> add our baggage
147+
// 3. Previous baggage header has sentry baggage values -> do nothing (might have been added manually by users)
148+
if (!prevBaggageHeader) {
146149
newHeaders.set('baggage', baggage);
150+
} else if (!baggageHeaderHasSentryBaggageValues(prevBaggageHeader)) {
151+
newHeaders.set('baggage', `${prevBaggageHeader},${baggage}`);
147152
}
148153
}
149154

150155
return newHeaders;
151-
} else if (Array.isArray(headers)) {
152-
const newHeaders = [
153-
...headers
154-
// Remove any existing sentry-trace headers
155-
.filter(header => {
156-
return !(Array.isArray(header) && header[0] === 'sentry-trace');
157-
})
158-
// Get rid of previous sentry baggage values in baggage header
159-
.map(header => {
160-
if (Array.isArray(header) && header[0] === 'baggage' && typeof header[1] === 'string') {
161-
const [headerName, headerValue, ...rest] = header;
162-
return [headerName, stripBaggageHeaderOfSentryBaggageValues(headerValue), ...rest];
163-
} else {
164-
return header;
165-
}
166-
}),
167-
// Attach the new sentry-trace header
168-
['sentry-trace', sentryTrace],
169-
];
156+
} else if (Array.isArray(originalHeaders)) {
157+
const newHeaders = [...originalHeaders];
170158

171-
if (baggage) {
159+
if (!originalHeaders.find(header => header[0] === 'sentry-trace')) {
160+
newHeaders.push(['sentry-trace', sentryTrace]);
161+
}
162+
163+
const prevBaggageHeaderWithSentryValues = originalHeaders.find(
164+
header => header[0] === 'baggage' && baggageHeaderHasSentryBaggageValues(header[1]),
165+
);
166+
167+
if (baggage && !prevBaggageHeaderWithSentryValues) {
172168
// If there are multiple entries with the same key, the browser will merge the values into a single request header.
173169
// Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
174170
newHeaders.push(['baggage', baggage]);
175171
}
176172

177173
return newHeaders as PolymorphicRequestHeaders;
178174
} else {
179-
const existingBaggageHeader = 'baggage' in headers ? headers.baggage : undefined;
180-
let newBaggageHeaders: string[] = [];
181-
182-
if (Array.isArray(existingBaggageHeader)) {
183-
newBaggageHeaders = existingBaggageHeader
184-
.map(headerItem =>
185-
typeof headerItem === 'string' ? stripBaggageHeaderOfSentryBaggageValues(headerItem) : headerItem,
186-
)
187-
.filter(headerItem => headerItem === '');
188-
} else if (existingBaggageHeader) {
189-
newBaggageHeaders.push(stripBaggageHeaderOfSentryBaggageValues(existingBaggageHeader));
190-
}
191-
192-
if (baggage) {
175+
const existingSentryTraceHeader = 'sentry-trace' in originalHeaders ? originalHeaders['sentry-trace'] : undefined;
176+
177+
const existingBaggageHeader = 'baggage' in originalHeaders ? originalHeaders.baggage : undefined;
178+
const newBaggageHeaders: string[] = existingBaggageHeader
179+
? Array.isArray(existingBaggageHeader)
180+
? [...existingBaggageHeader]
181+
: [existingBaggageHeader]
182+
: [];
183+
184+
const prevBaggageHeaderWithSentryValues =
185+
existingBaggageHeader &&
186+
(Array.isArray(existingBaggageHeader)
187+
? existingBaggageHeader.find(headerItem => baggageHeaderHasSentryBaggageValues(headerItem))
188+
: baggageHeaderHasSentryBaggageValues(existingBaggageHeader));
189+
190+
if (baggage && !prevBaggageHeaderWithSentryValues) {
193191
newBaggageHeaders.push(baggage);
194192
}
195193

196194
return {
197-
...(headers as Exclude<typeof headers, Headers>),
198-
'sentry-trace': sentryTrace,
195+
...(originalHeaders as Exclude<typeof originalHeaders, Headers>),
196+
'sentry-trace': (existingSentryTraceHeader as string | undefined) ?? sentryTrace,
199197
baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined,
200198
};
201199
}
@@ -219,14 +217,10 @@ function endSpan(span: Span, handlerData: HandlerDataFetch): void {
219217
span.end();
220218
}
221219

222-
function stripBaggageHeaderOfSentryBaggageValues(baggageHeader: string): string {
223-
return (
224-
baggageHeader
225-
.split(',')
226-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
227-
.filter(baggageEntry => !baggageEntry.split('=')[0]!.startsWith(SENTRY_BAGGAGE_KEY_PREFIX))
228-
.join(',')
229-
);
220+
function baggageHeaderHasSentryBaggageValues(baggageHeader: string): boolean {
221+
return baggageHeader
222+
.split(',')
223+
.some(baggageEntry => baggageEntry.split('=')[0]?.startsWith(SENTRY_BAGGAGE_KEY_PREFIX));
230224
}
231225

232226
function isHeaders(headers: unknown): headers is Headers {

0 commit comments

Comments
 (0)