Skip to content

Commit 07e8184

Browse files
committed
merge fetch baggage
1 parent 16d9e1d commit 07e8184

File tree

3 files changed

+71
-43
lines changed

3 files changed

+71
-43
lines changed

packages/node/src/integrations/http/SentryHttpInstrumentation.ts

+1-28
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@ import {
1818
getTraceData,
1919
httpRequestToRequestData,
2020
logger,
21-
objectToBaggageHeader,
22-
parseBaggageHeader,
2321
parseUrl,
2422
stripUrlQueryAndFragment,
2523
withIsolationScope,
2624
withScope,
2725
} from '@sentry/core';
2826
import { shouldPropagateTraceForUrl } from '@sentry/opentelemetry';
2927
import { DEBUG_BUILD } from '../../debug-build';
28+
import { mergeBaggageHeaders } from '../../utils/baggage';
3029
import { getRequestUrl } from '../../utils/getRequestUrl';
3130
import { getRequestInfo } from './vendor/getRequestInfo';
3231

@@ -602,29 +601,3 @@ function getAbsoluteUrl(origin: string, path: string = '/'): string {
602601
return `${url}${path}`;
603602
}
604603
}
605-
606-
function mergeBaggageHeaders(
607-
existing: string | string[] | number | undefined,
608-
baggage: string,
609-
): string | string[] | number | undefined {
610-
if (!existing) {
611-
return baggage;
612-
}
613-
614-
const existingBaggageEntries = parseBaggageHeader(existing);
615-
const newBaggageEntries = parseBaggageHeader(baggage);
616-
617-
if (!newBaggageEntries) {
618-
return existing;
619-
}
620-
621-
// Existing entries take precedence, ensuring order remains stable for minimal changes
622-
const mergedBaggageEntries = { ...existingBaggageEntries };
623-
Object.entries(newBaggageEntries).forEach(([key, value]) => {
624-
if (!mergedBaggageEntries[key]) {
625-
mergedBaggageEntries[key] = value;
626-
}
627-
});
628-
629-
return objectToBaggageHeader(mergedBaggageEntries);
630-
}

packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts

+39-15
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import { addBreadcrumb, getBreadcrumbLogLevelFromHttpStatusCode, getSanitizedUrl
77
import { shouldPropagateTraceForUrl } from '@sentry/opentelemetry';
88
import * as diagch from 'diagnostics_channel';
99
import { NODE_MAJOR, NODE_MINOR } from '../../nodeVersion';
10+
import { mergeBaggageHeaders } from '../../utils/baggage';
1011
import type { UndiciRequest, UndiciResponse } from './types';
1112

13+
const SENTRY_TRACE_HEADER = 'sentry-trace';
14+
const SENTRY_BAGGAGE_HEADER = 'baggage';
15+
1216
export type SentryNodeFetchInstrumentationOptions = InstrumentationConfig & {
1317
/**
1418
* Whether breadcrumbs should be recorded for requests.
@@ -18,8 +22,7 @@ export type SentryNodeFetchInstrumentationOptions = InstrumentationConfig & {
1822
breadcrumbs?: boolean;
1923

2024
/**
21-
* Do not capture breadcrumbs for outgoing fetch requests to URLs where the given callback returns `true`.
22-
* For the scope of this instrumentation, this callback only controls breadcrumb creation.
25+
* Do not capture breadcrumbs or inject headers for outgoing fetch requests to URLs where the given callback returns `true`.
2326
* The same option can be passed to the top-level httpIntegration where it controls both, breadcrumb and
2427
* span creation.
2528
*
@@ -127,26 +130,47 @@ export class SentryNodeFetchInstrumentation extends InstrumentationBase<SentryNo
127130
return;
128131
}
129132

133+
const { 'sentry-trace': sentryTrace, baggage } = addedHeaders;
134+
130135
// We do not want to overwrite existing headers here
131136
// If the core UndiciInstrumentation is registered, it will already have set the headers
132137
// We do not want to add any then
133138
if (Array.isArray(request.headers)) {
134139
const requestHeaders = request.headers;
135-
Object.entries(addedHeaders)
136-
.filter(([k]) => {
137-
// If the header already exists, we do not want to set it again
138-
return !requestHeaders.includes(k);
139-
})
140-
.forEach(keyValuePair => requestHeaders.push(...keyValuePair));
140+
141+
// We do not want to overwrite existing header here, if it was already set
142+
if (sentryTrace && !requestHeaders.includes(SENTRY_TRACE_HEADER)) {
143+
requestHeaders.push(SENTRY_TRACE_HEADER, sentryTrace);
144+
}
145+
146+
// For baggage, we make sure to merge this into a possibly existing header
147+
const existingBaggagePos = requestHeaders.findIndex(header => header === SENTRY_BAGGAGE_HEADER);
148+
if (baggage && existingBaggagePos === -1) {
149+
requestHeaders.push(SENTRY_BAGGAGE_HEADER, baggage);
150+
} else if (baggage) {
151+
const existingBaggage = requestHeaders[existingBaggagePos + 1];
152+
const merged = mergeBaggageHeaders(existingBaggage, baggage);
153+
if (merged) {
154+
requestHeaders[existingBaggagePos + 1] = merged;
155+
}
156+
}
141157
} else {
142158
const requestHeaders = request.headers;
143-
request.headers += Object.entries(addedHeaders)
144-
.filter(([k]) => {
145-
// If the header already exists, we do not want to set it again
146-
return !requestHeaders.includes(`${k}:`);
147-
})
148-
.map(([k, v]) => `${k}: ${v}\r\n`)
149-
.join('');
159+
// We do not want to overwrite existing header here, if it was already set
160+
if (sentryTrace && !requestHeaders.includes(`${SENTRY_TRACE_HEADER}:`)) {
161+
request.headers += `${SENTRY_TRACE_HEADER}: ${sentryTrace}\r\n`;
162+
}
163+
164+
// For baggage, we make sure to merge this into a possibly existing header
165+
const existingBaggage = request.headers.match(/baggage: (.*)\r\n/)?.[1];
166+
if (baggage && !existingBaggage) {
167+
request.headers += `${SENTRY_BAGGAGE_HEADER}: ${baggage}\r\n`;
168+
} else if (baggage) {
169+
const merged = mergeBaggageHeaders(existingBaggage, baggage);
170+
if (merged) {
171+
request.headers = request.headers.replace(/baggage: (.*)\r\n/, `baggage: ${merged}\r\n`);
172+
}
173+
}
150174
}
151175
}
152176

packages/node/src/utils/baggage.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { objectToBaggageHeader, parseBaggageHeader } from '@sentry/core';
2+
3+
/**
4+
* Merge two baggage headers into one, where the existing one takes precedence.
5+
* The order of the existing baggage will be preserved, and new entries will be added to the end.
6+
*/
7+
export function mergeBaggageHeaders<Existing extends string | string[] | number | undefined>(
8+
existing: Existing,
9+
baggage: string,
10+
): string | undefined | Existing {
11+
if (!existing) {
12+
return baggage;
13+
}
14+
15+
const existingBaggageEntries = parseBaggageHeader(existing);
16+
const newBaggageEntries = parseBaggageHeader(baggage);
17+
18+
if (!newBaggageEntries) {
19+
return existing;
20+
}
21+
22+
// Existing entries take precedence, ensuring order remains stable for minimal changes
23+
const mergedBaggageEntries = { ...existingBaggageEntries };
24+
Object.entries(newBaggageEntries).forEach(([key, value]) => {
25+
if (!mergedBaggageEntries[key]) {
26+
mergedBaggageEntries[key] = value;
27+
}
28+
});
29+
30+
return objectToBaggageHeader(mergedBaggageEntries);
31+
}

0 commit comments

Comments
 (0)