Skip to content
Merged
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
5 changes: 5 additions & 0 deletions lib/instrumentations.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ function onHttpClientRequestCreated({ request }) {

request[kSpan] = span;

// Node stores the headers inside the ClientRequest constructor when they
// are given as an array or include Expect, so setHeader would throw
// ERR_HTTP_HEADERS_SENT. The span is still recorded, without propagation.
if (request.headersSent) return;

request.setHeader('traceparent', span.toTraceparent());
// A stale tracestate from an unrelated trace must not travel with the
// new trace context.
Expand Down
55 changes: 55 additions & 0 deletions test/test-http-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,61 @@ describe('otel HTTP client spans', () => {
);
});

// Node has already stored the headers when the request is created with
// array headers or an Expect header, so injection is skipped rather than
// throwing ERR_HTTP_HEADERS_SENT.
for (const [title, path, getHeaders] of [
// Node adds no default Host header to array headers.
[
'array headers',
'/array-headers',
(port) => ['Host', `127.0.0.1:${port}`, 'x-dup', 'a', 'x-dup', 'b'],
],
['an Expect header', '/expect', () => ({ Expect: '100-continue' })],
]) {
it(`records a client span for a request with ${title}`, async () => {
let receivedTraceparent = 'sentinel';

const target = await startHTTPServer((req, res) => {
receivedTraceparent = req.headers.traceparent;
req.resume();
req.on('end', () => {
res.writeHead(200);
res.end('ok');
});
});

await new Promise((resolve, reject) => {
const req = http.request(
{
host: '127.0.0.1',
port: target.port,
method: 'POST',
path,
headers: getHeaders(target.port),
},
(res) => {
res.resume();
res.on('end', resolve);
},
);
req.on('error', reject);
req.end('body');
});

const spans = await flushAndCollect(collector);
await target.close();

assert.strictEqual(receivedTraceparent, undefined);
const clientSpan = getClientSpan(spans, path);
assert.ok(clientSpan, 'Expected a client span');
assert.strictEqual(
spanAttrs(clientSpan)['http.response.status_code'],
'200',
);
});
}

it('omits server.port when the Host port is not decimal', async () => {
const target = await startHTTPServer((req, res) => {
res.writeHead(200);
Expand Down
Loading