Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2

## Unreleased

* feat(b3-propagator)!: implement case-insensitive carrier handling [#5859](https://github.com/open-telemetry/opentelemetry-js/pull/5859) @YangJonghun

* feat(instrumentation-http): Added support for redacting specific url query string values and url credentials in instrumentations [#5743](https://github.com/open-telemetry/opentelemetry-js/pull/5743) @rads-1996

### :boom: Breaking Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,51 +303,43 @@ describe('fetch', () => {
exportedSpans = [];
});

const assertPropagationHeaders = async (
response: Response
): Promise<Record<string, string>> => {
const { request } = await response.json();

const assertPropagationHeaders = (response: Response): Headers => {
const span: tracing.ReadableSpan = exportedSpans[0];

assert.strictEqual(
request.headers[X_B3_TRACE_ID],
response.headers.get(X_B3_TRACE_ID),
span.spanContext().traceId,
`trace header '${X_B3_TRACE_ID}' not set`
);
assert.strictEqual(
request.headers[X_B3_SPAN_ID],
response.headers.get(X_B3_SPAN_ID),
span.spanContext().spanId,
`trace header '${X_B3_SPAN_ID}' not set`
);
assert.strictEqual(
request.headers[X_B3_SAMPLED],
response.headers.get(X_B3_SAMPLED),
String(span.spanContext().traceFlags),
`trace header '${X_B3_SAMPLED}' not set`
);

return request.headers;
return response.headers;
};

const assertNoPropagationHeaders = async (
response: Response
): Promise<Record<string, string>> => {
const { request } = await response.json();

const assertNoPropagationHeaders = (response: Response): Headers => {
assert.ok(
!(X_B3_TRACE_ID in request.headers),
!response.headers.has(X_B3_TRACE_ID),
`trace header '${X_B3_TRACE_ID}' should not be set`
);
assert.ok(
!(X_B3_SPAN_ID in request.headers),
!response.headers.has(X_B3_SPAN_ID),
`trace header '${X_B3_SPAN_ID}' should not be set`
);
assert.ok(
!(X_B3_SAMPLED in request.headers),
!response.headers.has(X_B3_SAMPLED),
`trace header '${X_B3_SAMPLED}' should not be set`
);

return request.headers;
return response.headers;
};

describe('same origin requests', () => {
Expand All @@ -357,11 +349,10 @@ describe('fetch', () => {
return msw.HttpResponse.json({ ok: true });
}),
msw.http.get('/api/echo-headers.json', ({ request }) => {
return msw.HttpResponse.json({
request: {
headers: Object.fromEntries(request.headers),
},
});
return msw.HttpResponse.json(
{ ok: true },
{ headers: request.headers }
);
}),
msw.http.get('/no-such-path', () => {
return new msw.HttpResponse(null, { status: 404 });
Expand Down Expand Up @@ -718,15 +709,15 @@ describe('fetch', () => {
callback: () => fetch('/api/echo-headers.json'),
});

await assertPropagationHeaders(response);
assertPropagationHeaders(response);
});

it('should set trace propagation headers with a request object', async () => {
const { response } = await tracedFetch({
callback: () => fetch(new Request('/api/echo-headers.json')),
});

await assertPropagationHeaders(response);
assertPropagationHeaders(response);
});

it('should keep custom headers with a request object and a headers object', async () => {
Expand All @@ -739,9 +730,9 @@ describe('fetch', () => {
),
});

const headers = await assertPropagationHeaders(response);
const headers = assertPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});

it('should keep custom headers with url, untyped request object and typed (Headers) headers object', async () => {
Expand All @@ -752,9 +743,9 @@ describe('fetch', () => {
}),
});

const headers = await assertPropagationHeaders(response);
const headers = assertPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});

it('should keep custom headers with url, untyped request object and untyped headers object', async () => {
Expand All @@ -765,9 +756,9 @@ describe('fetch', () => {
}),
});

const headers = await assertPropagationHeaders(response);
const headers = assertPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});

it('should keep custom headers with url, untyped request object and typed (Map) headers object', async () => {
Expand All @@ -779,9 +770,9 @@ describe('fetch', () => {
}),
});

const headers = await assertPropagationHeaders(response);
const headers = assertPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});
});

Expand All @@ -791,15 +782,15 @@ describe('fetch', () => {
callback: () => fetch('/api/echo-headers.json'),
});

await assertNoPropagationHeaders(response);
assertNoPropagationHeaders(response);
});

it('should not set trace propagation headers with a request object', async () => {
const { response } = await tracedFetch({
callback: () => fetch(new Request('/api/echo-headers.json')),
});

await assertNoPropagationHeaders(response);
assertNoPropagationHeaders(response);
});

it('should keep custom headers with a request object and a headers object', async () => {
Expand All @@ -814,7 +805,7 @@ describe('fetch', () => {

const headers = await assertNoPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});

it('should keep custom headers with url, untyped request object and typed (Headers) headers object', async () => {
Expand All @@ -827,7 +818,7 @@ describe('fetch', () => {

const headers = await assertNoPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});

it('should keep custom headers with url, untyped request object and untyped headers object', async () => {
Expand All @@ -840,7 +831,7 @@ describe('fetch', () => {

const headers = await assertNoPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});

it('should keep custom headers with url, untyped request object and typed (Map) headers object', async () => {
Expand All @@ -854,7 +845,7 @@ describe('fetch', () => {

const headers = await assertNoPropagationHeaders(response);

assert.strictEqual(headers['foo'], 'bar');
assert.strictEqual(headers.get('foo'), 'bar');
});
});
});
Expand Down Expand Up @@ -919,11 +910,10 @@ describe('fetch', () => {
msw.http.get(
'http://example.com/api/echo-headers.json',
({ request }) => {
return msw.HttpResponse.json({
request: {
headers: Object.fromEntries(request.headers),
},
});
return msw.HttpResponse.json(
{ ok: true },
{ headers: request.headers }
);
}
),
],
Expand Down Expand Up @@ -1161,7 +1151,7 @@ describe('fetch', () => {
},
});

await assertPropagationHeaders(response);
assertPropagationHeaders(response);

assertNoDebugMessages();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ describe('getPropagatorFromEnv', function () {
'tracestate',
'baggage',
'b3',
'x-b3-traceid',
'x-b3-spanid',
'x-b3-flags',
'x-b3-sampled',
'x-b3-parentspanid',
'X-B3-TraceId',
'X-B3-SpanId',
'X-B3-Flags',
'X-B3-Sampled',
'X-B3-ParentSpanId',
'uber-trace-id',
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ function parseHeader(header: unknown) {
}

function getHeaderValue(carrier: unknown, getter: TextMapGetter, key: string) {
const header = getter.get(carrier, key);
const header =
getter.get(carrier, key) ?? getter.get(carrier, key.toLowerCase());
return parseHeader(header);
}

Expand Down
10 changes: 5 additions & 5 deletions packages/opentelemetry-propagator-b3/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
export const B3_CONTEXT_HEADER = 'b3';

/* b3 multi-header keys */
export const X_B3_TRACE_ID = 'x-b3-traceid';
export const X_B3_SPAN_ID = 'x-b3-spanid';
export const X_B3_SAMPLED = 'x-b3-sampled';
export const X_B3_PARENT_SPAN_ID = 'x-b3-parentspanid';
export const X_B3_FLAGS = 'x-b3-flags';
export const X_B3_TRACE_ID = 'X-B3-TraceId';
export const X_B3_SPAN_ID = 'X-B3-SpanId';
export const X_B3_SAMPLED = 'X-B3-Sampled';
export const X_B3_PARENT_SPAN_ID = 'X-B3-ParentSpanId';
export const X_B3_FLAGS = 'X-B3-Flags';
101 changes: 101 additions & 0 deletions packages/opentelemetry-propagator-b3/test/B3MultiPropagator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,30 @@ describe('B3MultiPropagator', () => {
assert.strictEqual(carrier[X_B3_FLAGS], undefined);
assert.strictEqual(carrier[X_B3_PARENT_SPAN_ID], undefined);
});

it('should inject headers with proper case format', () => {
const spanContext: SpanContext = {
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
spanId: '6e0c63257de34c92',
traceFlags: TraceFlags.SAMPLED,
};

b3Propagator.inject(
trace.setSpanContext(ROOT_CONTEXT, spanContext),
carrier,
defaultTextMapSetter
);

// Verify headers are injected with standardized case format
assert.strictEqual(
carrier['X-B3-TraceId'],
'd4cda95b652f4a1592b449d5929fda1b'
);
assert.strictEqual(carrier['X-B3-SpanId'], '6e0c63257de34c92');
assert.strictEqual(carrier['X-B3-Sampled'], '1');
assert.strictEqual(carrier['x-b3-traceid'], undefined);
assert.strictEqual(carrier['x-b3-spanid'], undefined);
});
});

describe('.extract()', () => {
Expand Down Expand Up @@ -522,5 +546,82 @@ describe('B3MultiPropagator', () => {
});
assert.equal(context.getValue(B3_DEBUG_FLAG_KEY), undefined);
});

describe('case insensitive header extraction', () => {
it('should extract context from lowercase headers', () => {
carrier['x-b3-traceid'] = '0af7651916cd43dd8448eb211c80319c';
carrier['x-b3-spanid'] = 'b7ad6b7169203331';
carrier['x-b3-sampled'] = '1';
const context = b3Propagator.extract(
ROOT_CONTEXT,
carrier,
defaultTextMapGetter
);
const extractedSpanContext = trace.getSpanContext(context);
assert.deepStrictEqual(extractedSpanContext, {
spanId: 'b7ad6b7169203331',
traceId: '0af7651916cd43dd8448eb211c80319c',
isRemote: true,
traceFlags: TraceFlags.SAMPLED,
});
});

it('should extract context from mixed case headers', () => {
carrier['X-B3-TraceId'] = '0af7651916cd43dd8448eb211c80319c';
carrier['x-b3-spanid'] = 'b7ad6b7169203331';
carrier['X-b3-Sampled'] = '0';
const context = b3Propagator.extract(
ROOT_CONTEXT,
carrier,
defaultTextMapGetter
);
const extractedSpanContext = trace.getSpanContext(context);
assert.deepStrictEqual(extractedSpanContext, {
spanId: 'b7ad6b7169203331',
traceId: '0af7651916cd43dd8448eb211c80319c',
isRemote: true,
traceFlags: TraceFlags.NONE,
});
});

it('should prioritize standard case over lowercase when both exist', () => {
carrier[X_B3_TRACE_ID] = '0af7651916cd43dd8448eb211c80319c';
carrier['x-b3-traceid'] = 'ffffffffffffffffffffffffffffffff';
carrier[X_B3_SPAN_ID] = 'b7ad6b7169203331';
carrier[X_B3_SAMPLED] = '1';
const context = b3Propagator.extract(
ROOT_CONTEXT,
carrier,
defaultTextMapGetter
);
const extractedSpanContext = trace.getSpanContext(context);
// Should use the standard case header value, not lowercase
assert.deepStrictEqual(extractedSpanContext, {
spanId: 'b7ad6b7169203331',
traceId: '0af7651916cd43dd8448eb211c80319c',
isRemote: true,
traceFlags: TraceFlags.SAMPLED,
});
});

it('should extract context from lowercase debug flag', () => {
carrier['x-b3-traceid'] = '0af7651916cd43dd8448eb211c80319c';
carrier['x-b3-spanid'] = 'b7ad6b7169203331';
carrier['x-b3-flags'] = '1';
const context = b3Propagator.extract(
ROOT_CONTEXT,
carrier,
defaultTextMapGetter
);
const extractedSpanContext = trace.getSpanContext(context);
assert.deepStrictEqual(extractedSpanContext, {
spanId: 'b7ad6b7169203331',
traceId: '0af7651916cd43dd8448eb211c80319c',
isRemote: true,
traceFlags: TraceFlags.SAMPLED,
});
assert.strictEqual(context.getValue(B3_DEBUG_FLAG_KEY), '1');
});
});
});
});
Loading