Skip to content

Commit f5e75df

Browse files
committed
wip
1 parent 66b48de commit f5e75df

File tree

4 files changed

+96
-18
lines changed

4 files changed

+96
-18
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { objectToBaggageHeader, parseBaggageHeader } from '@sentry/core';
1+
import { objectToBaggageHeader, parseBaggageHeader, SENTRY_BAGGAGE_KEY_PREFIX } from '@sentry/core';
22

33
/**
44
* Merge two baggage headers into one.
@@ -29,7 +29,7 @@ export function mergeBaggageHeaders<Existing extends string | string[] | number
2929
Object.entries(newBaggageEntries).forEach(([key, value]) => {
3030
// Sentry-specific keys always take precedence from new baggage
3131
// Non-Sentry keys only added if not already present
32-
if (key.startsWith('sentry-') || !mergedBaggageEntries[key]) {
32+
if (key.startsWith(SENTRY_BAGGAGE_KEY_PREFIX) || !mergedBaggageEntries[key]) {
3333
mergedBaggageEntries[key] = value;
3434
}
3535
});

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

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { mergeBaggageHeaders } from './baggage';
1414
const SENTRY_TRACE_HEADER = 'sentry-trace';
1515
const SENTRY_BAGGAGE_HEADER = 'baggage';
1616

17-
// For baggage, we make sure to merge this into a possibly existing header
18-
const BAGGAGE_HEADER_REGEX = /baggage: (.*)\r\n/;
17+
const BAGGAGE_HEADER_REGEX_GLOBAL = /baggage: (.*)\r\n/g;
18+
const SENTRY_TRACE_HEADER_REGEX_GLOBAL = /sentry-trace: .*\r\n/g;
1919

2020
/**
2121
* Add trace propagation headers to an outgoing fetch/undici request.
@@ -60,15 +60,44 @@ export function addTracePropagationHeadersToFetchRequest(
6060
requestHeaders.push('traceparent', traceparent);
6161
}
6262

63-
// For baggage, we make sure to merge this into a possibly existing header
64-
const existingBaggagePos = requestHeaders.findIndex(header => header === SENTRY_BAGGAGE_HEADER);
65-
if (baggage && existingBaggagePos === -1) {
63+
// Consolidate all duplicate baggage entries into one, then merge with our new baggage.
64+
// OTel's UndiciInstrumentation may append a second baggage header via propagation.inject(),
65+
// so we need to handle multiple entries — not just the first one.
66+
const baggagePositions: number[] = [];
67+
for (let i = 0; i < requestHeaders.length; i++) {
68+
if (requestHeaders[i] === SENTRY_BAGGAGE_HEADER) {
69+
baggagePositions.push(i);
70+
}
71+
}
72+
73+
if (baggage && !baggagePositions.length) {
6674
requestHeaders.push(SENTRY_BAGGAGE_HEADER, baggage);
6775
} else if (baggage) {
68-
const existingBaggage = requestHeaders[existingBaggagePos + 1];
69-
const merged = mergeBaggageHeaders(existingBaggage, baggage);
76+
// First, consolidate all existing baggage values into one
77+
let consolidatedBaggage = requestHeaders[baggagePositions[0]! + 1] as string;
78+
for (let i = baggagePositions.length - 1; i >= 1; i--) {
79+
const pos = baggagePositions[i]!;
80+
const val = requestHeaders[pos + 1] as string;
81+
consolidatedBaggage = mergeBaggageHeaders(consolidatedBaggage, val) || consolidatedBaggage;
82+
requestHeaders.splice(pos, 2);
83+
}
84+
85+
// Then merge with the new baggage we want to add
86+
const merged = mergeBaggageHeaders(consolidatedBaggage, baggage);
7087
if (merged) {
71-
requestHeaders[existingBaggagePos + 1] = merged;
88+
requestHeaders[baggagePositions[0]! + 1] = merged;
89+
}
90+
}
91+
92+
// Also deduplicate sentry-trace headers — keep only the first occurrence.
93+
// OTel's UndiciInstrumentation may have appended a second one via propagation.inject().
94+
let firstSentryTraceFound = false;
95+
for (let i = requestHeaders.length - 2; i >= 0; i--) {
96+
if (requestHeaders[i] === SENTRY_TRACE_HEADER) {
97+
if (firstSentryTraceFound) {
98+
requestHeaders.splice(i, 2);
99+
}
100+
firstSentryTraceFound = true;
72101
}
73102
}
74103
} else {
@@ -82,15 +111,37 @@ export function addTracePropagationHeadersToFetchRequest(
82111
request.headers += `traceparent: ${traceparent}\r\n`;
83112
}
84113

85-
const existingBaggage = request.headers.match(BAGGAGE_HEADER_REGEX)?.[1];
86-
if (baggage && !existingBaggage) {
114+
// Consolidate all duplicate baggage entries into one, then merge with our new baggage.
115+
// OTel's UndiciInstrumentation may append a second baggage header via propagation.inject(),
116+
// so we need to handle multiple entries — not just the first one.
117+
const allBaggageMatches = request.headers.matchAll(BAGGAGE_HEADER_REGEX_GLOBAL);
118+
let consolidatedBaggage: string | undefined;
119+
for (const match of allBaggageMatches) {
120+
if (match[1]) {
121+
consolidatedBaggage = consolidatedBaggage
122+
? mergeBaggageHeaders(consolidatedBaggage, match[1]) || consolidatedBaggage
123+
: match[1];
124+
}
125+
}
126+
127+
// Remove all existing baggage entries
128+
request.headers = request.headers.replace(BAGGAGE_HEADER_REGEX_GLOBAL, '');
129+
130+
if (baggage && !consolidatedBaggage) {
87131
request.headers += `${SENTRY_BAGGAGE_HEADER}: ${baggage}\r\n`;
88-
} else if (baggage) {
89-
const merged = mergeBaggageHeaders(existingBaggage, baggage);
132+
} else if (baggage && consolidatedBaggage) {
133+
const merged = mergeBaggageHeaders(consolidatedBaggage, baggage);
90134
if (merged) {
91-
request.headers = request.headers.replace(BAGGAGE_HEADER_REGEX, `baggage: ${merged}\r\n`);
135+
request.headers += `${SENTRY_BAGGAGE_HEADER}: ${merged}\r\n`;
92136
}
93137
}
138+
139+
// Deduplicate sentry-trace headers — keep only the first occurrence.
140+
let sentryTraceCount = 0;
141+
request.headers = request.headers.replace(SENTRY_TRACE_HEADER_REGEX_GLOBAL, match => {
142+
sentryTraceCount++;
143+
return sentryTraceCount === 1 ? match : '';
144+
});
94145
}
95146
}
96147

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ export function addTracePropagationHeadersToOutgoingRequest(
9292
}
9393

9494
if (baggage) {
95-
const newBaggage = mergeBaggageHeaders(request.getHeader('baggage'), baggage);
95+
const existingBaggage = request.getHeader('baggage');
96+
97+
const newBaggage = mergeBaggageHeaders(existingBaggage, baggage);
9698
if (newBaggage) {
9799
try {
98100
request.setHeader('baggage', newBaggage);

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('mergeBaggageHeaders', () => {
8787
it('handles array-type existing baggage', () => {
8888
const result = mergeBaggageHeaders(['foo=bar', 'other=vendor'], 'sentry-release=1.0.0');
8989

90-
const entries = result?.split(',');
90+
const entries = (result as string)?.split(',');
9191
expect(entries).toContain('foo=bar');
9292
expect(entries).toContain('other=vendor');
9393
expect(entries).toContain('sentry-release=1.0.0');
@@ -115,7 +115,7 @@ describe('mergeBaggageHeaders', () => {
115115
expect(entries).not.toContain('sentry-environment=old');
116116
});
117117

118-
it('matches OTEL propagation.inject() behavior for Sentry keys', () => {
118+
it('overwrites existing Sentry entries with new SDK values', () => {
119119
const result = mergeBaggageHeaders(
120120
'sentry-trace_id=abc123,sentry-sampled=false,non-sentry=keep',
121121
'sentry-trace_id=xyz789,sentry-sampled=true',
@@ -128,4 +128,29 @@ describe('mergeBaggageHeaders', () => {
128128
expect(entries).not.toContain('sentry-trace_id=abc123');
129129
expect(entries).not.toContain('sentry-sampled=false');
130130
});
131+
132+
it('merges non-conflicting baggage entries', () => {
133+
const existing = 'custom-key=value';
134+
const newBaggage = 'sentry-environment=production';
135+
const result = mergeBaggageHeaders(existing, newBaggage);
136+
expect(result).toContain('custom-key=value');
137+
expect(result).toContain('sentry-environment=production');
138+
});
139+
140+
it('overwrites existing Sentry entries when keys conflict', () => {
141+
const existing = 'sentry-environment=staging';
142+
const newBaggage = 'sentry-environment=production';
143+
const result = mergeBaggageHeaders(existing, newBaggage);
144+
expect(result).toBe('sentry-environment=production');
145+
});
146+
147+
it('handles multiple entries with Sentry conflicts', () => {
148+
const existing = 'custom-key=value1,sentry-environment=staging';
149+
const newBaggage = 'sentry-environment=production,sentry-trace_id=123';
150+
const result = mergeBaggageHeaders(existing, newBaggage);
151+
expect(result).toContain('custom-key=value1');
152+
expect(result).toContain('sentry-environment=production');
153+
expect(result).toContain('sentry-trace_id=123');
154+
expect(result).not.toContain('sentry-environment=staging');
155+
});
131156
});

0 commit comments

Comments
 (0)