Skip to content

Commit 5e81f57

Browse files
committed
convert undici node 18 string headers to array to dedupe and then back to string
1 parent 0e19dc1 commit 5e81f57

File tree

4 files changed

+48
-29
lines changed

4 files changed

+48
-29
lines changed

dev-packages/node-integration-tests/suites/tracing/double-baggage-no-spans/test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('double baggage prevention', () => {
4545
const [SERVER_URL, closeTestServer] = await createTestServer()
4646
.get('/api/fetch-custom-headers', headers => {
4747
// fetch with manual getTraceData() headers
48+
// console.log('xx sentry-tace', headers['sentry-trace']);
4849
expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^[a-f\d]{32}-[a-f\d]{16}$/));
4950
expectNoDuplicateSentryBaggageKeys(headers['baggage']);
5051
expectConsistentTraceId(headers);

dev-packages/node-integration-tests/suites/tracing/double-baggage-spans-parent/test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ describe('double baggage prevention - http.client spans with parent span', () =>
4949
const [SERVER_URL, closeTestServer] = await createTestServer()
5050
.get('/api/fetch-custom-headers', headers => {
5151
// fetch with manual getTraceData() headers — core reproduction case
52+
const sentryTrace = extractTraceparentData(headers['sentry-trace'] as string);
53+
transactionTraceId = sentryTrace!.traceId!;
54+
fetchCustomHeadersSpanId = sentryTrace!.parentSpanId!;
5255
expectNoDuplicateSentryBaggageKeys(headers['baggage']);
5356
expect(headers['sentry-trace']).not.toContain(',');
5457
expectConsistentTraceId(headers);
5558
expectUserSetTraceId(headers);
56-
const sentryTrace = extractTraceparentData(headers['sentry-trace'] as string);
57-
transactionTraceId = sentryTrace!.traceId!;
58-
fetchCustomHeadersSpanId = sentryTrace!.parentSpanId!;
5959
})
6060
.get('/api/fetch', headers => {
6161
// fetch without manual headers (baseline)

packages/node-core/src/utils/outgoingFetchRequest.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,29 @@ function _deduplicateHeaders(request: UndiciRequest): void {
121121
if (Array.isArray(request.headers)) {
122122
_deduplicateArrayHeaders(request.headers);
123123
} else {
124-
request.headers = _deduplicateStringHeaders(request.headers);
124+
const headersArray = request.headers.split('\r\n');
125+
const headers: string[] = [];
126+
for (const header of headersArray) {
127+
try {
128+
const [key, value] = header.split(':').map(part => part.trim());
129+
if (key != null && value != null) {
130+
headers.push(key, value);
131+
}
132+
} catch {
133+
continue;
134+
}
135+
}
136+
137+
_deduplicateArrayHeaders(headers);
138+
139+
const headerPairs: string[] = [];
140+
for (let i = 0; i < headers.length; i += 2) {
141+
headerPairs.push(`${headers[i]}: ${headers[i + 1]}`);
142+
}
143+
const concatenated = headerPairs.join('\r\n');
144+
if (concatenated) {
145+
request.headers = concatenated.concat('\r\n');
146+
}
125147
}
126148
}
127149

@@ -161,31 +183,6 @@ function _deduplicateArrayHeader(headers: string[], headerName: string): void {
161183
}
162184
}
163185

164-
function _deduplicateStringHeaders(input: string): string {
165-
// Deduplicate sentry-trace — keep only the first occurrence
166-
let sentryTraceCount = 0;
167-
let result = input.replace(/sentry-trace: .*\r\n/g, match => {
168-
return ++sentryTraceCount === 1 ? match : '';
169-
});
170-
171-
// Deduplicate baggage — merge all occurrences into one but preserve initial sentry- values
172-
let mergedBaggage: string | undefined;
173-
result = result.replace(/baggage: (.*)\r\n/g, (_match, value: string) => {
174-
if (!mergedBaggage) {
175-
mergedBaggage = value;
176-
} else {
177-
mergedBaggage = mergeBaggageHeaders(value, mergedBaggage) || mergedBaggage;
178-
}
179-
return '';
180-
});
181-
182-
if (mergedBaggage) {
183-
result += `${SENTRY_BAGGAGE_HEADER}: ${mergedBaggage}\r\n`;
184-
}
185-
186-
return result;
187-
}
188-
189186
/** Add a breadcrumb for an outgoing fetch/undici request. */
190187
export function addFetchRequestBreadcrumb(request: UndiciRequest, response: UndiciResponse): void {
191188
const data = getBreadcrumbData(request);

packages/node-core/test/utils/outgoingFetchRequest.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,5 +304,26 @@ describe('addTracePropagationHeadersToFetchRequest', () => {
304304
);
305305
});
306306
});
307+
308+
it("doesn't dedupe nearly-sentry-tracing headers", () => {
309+
const request = {
310+
headers:
311+
'sentry-trace: user-trace_id-xyz-1\r\n' +
312+
'baggage: sentry-trace_id=user-trace_id,sentry-sampled=true,sentry-environment=user\r\n' +
313+
'x-sentry-trace: custom-trace_id-abc-1\r\n' +
314+
'x-baggage: sentry-trace_id=undici-trace_id-abc-1,sentry-sampled=true,sentry-environment=undici\r\n',
315+
origin: 'https://some-service.com',
316+
path: '/api/test',
317+
} as UndiciRequest;
318+
319+
addTracePropagationHeadersToFetchRequest(request, new LRUMap<string, boolean>(100));
320+
321+
expect(request.headers).toBe(
322+
'sentry-trace: user-trace_id-xyz-1\r\n' +
323+
'baggage: sentry-trace_id=user-trace_id,sentry-sampled=true,sentry-environment=user\r\n' +
324+
'x-sentry-trace: custom-trace_id-abc-1\r\n' +
325+
'x-baggage: sentry-trace_id=undici-trace_id-abc-1,sentry-sampled=true,sentry-environment=undici\r\n',
326+
);
327+
});
307328
});
308329
});

0 commit comments

Comments
 (0)