Skip to content

Commit 45fa810

Browse files
authored
feat(core): Export a reusable function to add tracing headers (#20076)
This PR is an extraction of #19991 It basically exports `getTracingHeadersForFetchRequest`, which was previously only exported for testing, but offers a great functionality if you want to add tracing headers to a request. I renamed it as `addTracingHeadersToFetchRequest` sounded a little misleading, as it didn't really add headers to the request, as it returned the extracted headers from the request (or init, if there are any). ### Open question I added `@hidden` and `@internal` to it, not sure if this is an approach we follow. I'm ok to remove it from the jsdoc
1 parent 2897297 commit 45fa810

File tree

3 files changed

+84
-48
lines changed

3 files changed

+84
-48
lines changed

packages/core/src/fetch.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import {
1919
} from './utils/url';
2020

2121
type PolymorphicRequestHeaders =
22-
| Record<string, string | undefined>
23-
| Array<[string, string]>
22+
| Record<string, unknown>
23+
| Array<[string, unknown]>
24+
| Iterable<Iterable<unknown>>
2425
// the below is not precisely the Header type used in Request, but it'll pass duck-typing
2526
| {
2627
append: (key: string, value: string) => void;
@@ -124,7 +125,7 @@ export function instrumentFetchRequest(
124125
// Examples: users re-using same options object for multiple fetch calls, frozen objects
125126
const options: { [key: string]: unknown } = { ...(handlerData.args[1] || {}) };
126127

127-
const headers = _addTracingHeadersToFetchRequest(
128+
const headers = _INTERNAL_getTracingHeadersForFetchRequest(
128129
request,
129130
options,
130131
// If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction),
@@ -176,17 +177,21 @@ export function _callOnRequestSpanEnd(
176177
}
177178

178179
/**
179-
* Adds sentry-trace and baggage headers to the various forms of fetch headers.
180-
* exported only for testing purposes
180+
* Builds merged fetch headers that include `sentry-trace` and `baggage` (and optionally `traceparent`)
181+
* for the given request and init, without mutating the original request or options.
182+
* Returns `undefined` when there is no `sentry-trace` value to attach.
181183
*
182-
* When we determine if we should add a baggage header, there are 3 cases:
183-
* 1. No previous baggage header -> add baggage
184-
* 2. Previous baggage header has no sentry baggage values -> add our baggage
185-
* 3. Previous baggage header has sentry baggage values -> do nothing (might have been added manually by users)
184+
* @internal Exported for cross-package instrumentation (for example Cloudflare Workers fetcher bindings)
185+
* and unit tests
186+
*
187+
* Baggage handling:
188+
* 1. No previous baggage header → include Sentry baggage
189+
* 2. Previous baggage has no Sentry entries → merge Sentry baggage in
190+
* 3. Previous baggage already has Sentry entries → leave as-is (may be user-defined)
186191
*/
187192
// eslint-disable-next-line complexity -- yup it's this complicated :(
188-
export function _addTracingHeadersToFetchRequest(
189-
request: string | Request,
193+
export function _INTERNAL_getTracingHeadersForFetchRequest(
194+
request: string | URL | Request,
190195
fetchOptionsObj: {
191196
headers?:
192197
| {
@@ -234,19 +239,20 @@ export function _addTracingHeadersToFetchRequest(
234239
}
235240

236241
return newHeaders;
237-
} else if (Array.isArray(originalHeaders)) {
242+
} else if (isHeadersInitTupleArray(originalHeaders)) {
238243
const newHeaders = [...originalHeaders];
239244

240-
if (!originalHeaders.find(header => header[0] === 'sentry-trace')) {
245+
if (!newHeaders.find(header => header[0] === 'sentry-trace')) {
241246
newHeaders.push(['sentry-trace', sentryTrace]);
242247
}
243248

244-
if (propagateTraceparent && traceparent && !originalHeaders.find(header => header[0] === 'traceparent')) {
249+
if (propagateTraceparent && traceparent && !newHeaders.find(header => header[0] === 'traceparent')) {
245250
newHeaders.push(['traceparent', traceparent]);
246251
}
247252

248253
const prevBaggageHeaderWithSentryValues = originalHeaders.find(
249-
header => header[0] === 'baggage' && baggageHeaderHasSentryBaggageValues(header[1]),
254+
header =>
255+
header[0] === 'baggage' && typeof header[1] === 'string' && baggageHeaderHasSentryBaggageValues(header[1]),
250256
);
251257

252258
if (baggage && !prevBaggageHeaderWithSentryValues) {
@@ -255,7 +261,7 @@ export function _addTracingHeadersToFetchRequest(
255261
newHeaders.push(['baggage', baggage]);
256262
}
257263

258-
return newHeaders as PolymorphicRequestHeaders;
264+
return newHeaders;
259265
} else {
260266
const existingSentryTraceHeader = 'sentry-trace' in originalHeaders ? originalHeaders['sentry-trace'] : undefined;
261267
const existingTraceparentHeader = 'traceparent' in originalHeaders ? originalHeaders.traceparent : undefined;
@@ -313,14 +319,29 @@ function endSpan(span: Span, handlerData: HandlerDataFetch): void {
313319
span.end();
314320
}
315321

316-
function baggageHeaderHasSentryBaggageValues(baggageHeader: string): boolean {
322+
function baggageHeaderHasSentryBaggageValues(baggageHeader: unknown): boolean {
323+
if (typeof baggageHeader !== 'string') {
324+
return false;
325+
}
326+
317327
return baggageHeader.split(',').some(baggageEntry => baggageEntry.trim().startsWith(SENTRY_BAGGAGE_KEY_PREFIX));
318328
}
319329

320330
function isHeaders(headers: unknown): headers is Headers {
321331
return typeof Headers !== 'undefined' && isInstanceOf(headers, Headers);
322332
}
323333

334+
/** `HeadersInit` array form: each entry is a [name, value] pair of strings. */
335+
function isHeadersInitTupleArray(headers: unknown): headers is [string, unknown][] {
336+
if (!Array.isArray(headers)) {
337+
return false;
338+
}
339+
340+
return headers.every(
341+
(item): item is [string, unknown] => Array.isArray(item) && item.length === 2 && typeof item[0] === 'string',
342+
);
343+
}
344+
324345
function getSpanStartOptions(
325346
url: string,
326347
method: string,

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export { profiler } from './profiling';
147147
// eslint thinks the entire function is deprecated (while only one overload is actually deprecated)
148148
// Therefore:
149149
// eslint-disable-next-line deprecation/deprecation
150-
export { instrumentFetchRequest } from './fetch';
150+
export { instrumentFetchRequest, _INTERNAL_getTracingHeadersForFetchRequest } from './fetch';
151151
export { trpcMiddleware } from './trpc';
152152
export { wrapMcpServerWithSentry } from './integrations/mcp-server';
153153
export { captureFeedback } from './feedback';

packages/core/test/lib/fetch.test.ts

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest';
22
import type { HandlerDataFetch } from '../../src';
3-
import { _addTracingHeadersToFetchRequest, instrumentFetchRequest } from '../../src/fetch';
3+
import { _INTERNAL_getTracingHeadersForFetchRequest, instrumentFetchRequest } from '../../src/fetch';
44
import type { Span } from '../../src/types-hoist/span';
55

66
const { DEFAULT_SENTRY_TRACE, DEFAULT_BAGGAGE, hasSpansEnabled } = vi.hoisted(() => ({
@@ -31,7 +31,7 @@ vi.mock('../../src/utils/hasSpansEnabled', () => {
3131
};
3232
});
3333

34-
describe('_addTracingHeadersToFetchRequest', () => {
34+
describe('_INTERNAL_getTracingHeadersForFetchRequest', () => {
3535
beforeEach(() => {
3636
vi.clearAllMocks();
3737
hasSpansEnabled.mockReturnValue(false);
@@ -47,7 +47,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
4747
options: { headers: {} },
4848
},
4949
])('attaches sentry headers (options: $options)', ({ options }) => {
50-
expect(_addTracingHeadersToFetchRequest('/api/test', options)).toEqual({
50+
expect(_INTERNAL_getTracingHeadersForFetchRequest('/api/test', options)).toEqual({
5151
'sentry-trace': DEFAULT_SENTRY_TRACE,
5252
baggage: DEFAULT_BAGGAGE,
5353
});
@@ -56,17 +56,17 @@ describe('_addTracingHeadersToFetchRequest', () => {
5656

5757
describe('and request headers are set in options', () => {
5858
it('attaches sentry headers to headers object', () => {
59-
expect(_addTracingHeadersToFetchRequest('/api/test', { headers: { 'custom-header': 'custom-value' } })).toEqual(
60-
{
61-
'sentry-trace': DEFAULT_SENTRY_TRACE,
62-
baggage: DEFAULT_BAGGAGE,
63-
'custom-header': 'custom-value',
64-
},
65-
);
59+
expect(
60+
_INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: { 'custom-header': 'custom-value' } }),
61+
).toEqual({
62+
'sentry-trace': DEFAULT_SENTRY_TRACE,
63+
baggage: DEFAULT_BAGGAGE,
64+
'custom-header': 'custom-value',
65+
});
6666
});
6767

6868
it('attaches sentry headers to a Headers instance', () => {
69-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
69+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
7070
headers: new Headers({ 'custom-header': 'custom-value' }),
7171
});
7272

@@ -81,7 +81,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
8181
});
8282

8383
it('attaches sentry headers to headers array', () => {
84-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
84+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
8585
headers: [['custom-header', 'custom-value']],
8686
});
8787

@@ -92,11 +92,26 @@ describe('_addTracingHeadersToFetchRequest', () => {
9292
['baggage', DEFAULT_BAGGAGE],
9393
]);
9494
});
95+
96+
it('treats array with non-tuple items as headers object', () => {
97+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
98+
headers: ['not-a-tuple', 'also-not-a-tuple'],
99+
});
100+
101+
// Falls through to the else branch (headers object handling)
102+
// since the array items are not [string, string] tuples
103+
expect(returnedHeaders).toEqual({
104+
'0': 'not-a-tuple',
105+
'1': 'also-not-a-tuple',
106+
'sentry-trace': DEFAULT_SENTRY_TRACE,
107+
baggage: DEFAULT_BAGGAGE,
108+
});
109+
});
95110
});
96111

97112
describe('and 3rd party baggage header is set', () => {
98113
it('adds additional sentry baggage values to Headers instance', () => {
99-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
114+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
100115
headers: new Headers({
101116
baggage: 'custom-baggage=1,someVal=bar',
102117
}),
@@ -112,7 +127,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
112127
});
113128

114129
it('adds additional sentry baggage values to headers array', () => {
115-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
130+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
116131
headers: [['baggage', 'custom-baggage=1,someVal=bar']],
117132
});
118133

@@ -126,7 +141,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
126141
});
127142

128143
it('adds additional sentry baggage values to headers object', () => {
129-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
144+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
130145
headers: {
131146
baggage: 'custom-baggage=1,someVal=bar',
132147
},
@@ -141,7 +156,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
141156
});
142157

143158
it('adds additional sentry baggage values to headers object with arrays', () => {
144-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
159+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
145160
headers: {
146161
baggage: ['custom-baggage=1,someVal=bar', 'other-vendor-key=value'],
147162
},
@@ -158,7 +173,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
158173

159174
describe('and Sentry values are already set', () => {
160175
it('does not override them (Headers instance)', () => {
161-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
176+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
162177
headers: new Headers({
163178
'sentry-trace': CUSTOM_SENTRY_TRACE,
164179
baggage: CUSTOM_BAGGAGE,
@@ -177,7 +192,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
177192
});
178193

179194
it('does not override them (headers array)', () => {
180-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
195+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
181196
headers: [
182197
['sentry-trace', CUSTOM_SENTRY_TRACE],
183198
['baggage', CUSTOM_BAGGAGE],
@@ -195,7 +210,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
195210
});
196211

197212
it('does not override them (headers object)', () => {
198-
const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', {
213+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', {
199214
headers: {
200215
'sentry-trace': CUSTOM_SENTRY_TRACE,
201216
baggage: CUSTOM_BAGGAGE,
@@ -218,7 +233,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
218233
describe('and no request headers are set', () => {
219234
it('attaches sentry headers', () => {
220235
const request = new Request('http://locahlost:3000/api/test');
221-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
236+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
222237

223238
expect(returnedHeaders).toBeInstanceOf(Headers);
224239

@@ -236,7 +251,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
236251
headers: new Headers({ 'custom-header': 'custom-value' }),
237252
});
238253

239-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
254+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
240255

241256
expect(returnedHeaders).toBeInstanceOf(Headers);
242257

@@ -253,7 +268,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
253268
headers: { 'custom-header': 'custom-value' },
254269
});
255270

256-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
271+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
257272

258273
expect(returnedHeaders).toBeInstanceOf(Headers);
259274

@@ -270,7 +285,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
270285
headers: [['custom-header', 'custom-value']],
271286
});
272287

273-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
288+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
274289

275290
expect(returnedHeaders).toBeInstanceOf(Headers);
276291

@@ -292,7 +307,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
292307
}),
293308
});
294309

295-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
310+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
296311

297312
expect(returnedHeaders).toBeInstanceOf(Headers);
298313

@@ -309,7 +324,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
309324
headers: [['baggage', 'custom-baggage=1,someVal=bar']],
310325
});
311326

312-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
327+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
313328

314329
expect(returnedHeaders).toBeInstanceOf(Headers);
315330

@@ -327,7 +342,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
327342
},
328343
});
329344

330-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
345+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
331346

332347
expect(returnedHeaders).toBeInstanceOf(Headers);
333348

@@ -345,7 +360,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
345360
},
346361
});
347362

348-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
363+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
349364

350365
expect(returnedHeaders).toBeInstanceOf(Headers);
351366

@@ -367,7 +382,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
367382
}),
368383
});
369384

370-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
385+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
371386

372387
expect(returnedHeaders).toBeInstanceOf(Headers);
373388

@@ -388,7 +403,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
388403
],
389404
});
390405

391-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
406+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
392407

393408
expect(returnedHeaders).toBeInstanceOf(Headers);
394409

@@ -409,7 +424,7 @@ describe('_addTracingHeadersToFetchRequest', () => {
409424
},
410425
});
411426

412-
const returnedHeaders = _addTracingHeadersToFetchRequest(request, {});
427+
const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {});
413428

414429
expect(returnedHeaders).toBeInstanceOf(Headers);
415430

0 commit comments

Comments
 (0)