Skip to content

Commit 4b4ac76

Browse files
logaretmclaudeCopilot
authored
fix(node): Guard against null httpVersion in outgoing request span attributes (#20430)
It appears that `httpVersion` can be `undefined` in some cases, and even tho the node types don't suggest that we do seem to treat it as such in other parts of the code so I assume we have run into this before. This PR guards against this specific usage here as this is the only place where it wasn't guarded. Fixes #20415 --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 7569b10 commit 4b4ac76

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,10 +456,16 @@ function _getOutgoingRequestSpanData(request: http.ClientRequest): [string, Span
456456
];
457457
}
458458

459-
function _getOutgoingRequestEndedSpanData(response: http.IncomingMessage): SpanAttributes {
459+
/**
460+
* Exported for testing purposes.
461+
*/
462+
export function _getOutgoingRequestEndedSpanData(response: http.IncomingMessage): SpanAttributes {
460463
const { statusCode, statusMessage, httpVersion, socket } = response;
461464

462-
const transport = httpVersion.toUpperCase() !== 'QUIC' ? 'ip_tcp' : 'ip_udp';
465+
// httpVersion can be undefined in some cases and we seem to have encountered this before:
466+
// https://github.com/getsentry/sentry-javascript/blob/ec8c8c64cde6001123db0199a8ca017b8863eac8/packages/node-core/src/integrations/http/httpServerSpansIntegration.ts#L158
467+
// see: #20415
468+
const transport = httpVersion?.toUpperCase() !== 'QUIC' ? 'ip_tcp' : 'ip_udp';
463469

464470
const additionalAttributes: SpanAttributes = {
465471
[ATTR_HTTP_RESPONSE_STATUS_CODE]: statusCode,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type * as http from 'node:http';
2+
import { describe, expect, it } from 'vitest';
3+
import { _getOutgoingRequestEndedSpanData } from '../../src/integrations/http/SentryHttpInstrumentation';
4+
5+
function createResponse(overrides: Partial<http.IncomingMessage>): http.IncomingMessage {
6+
return {
7+
statusCode: 200,
8+
statusMessage: 'OK',
9+
httpVersion: '1.1',
10+
headers: {},
11+
socket: undefined,
12+
...overrides,
13+
} as unknown as http.IncomingMessage;
14+
}
15+
16+
describe('_getOutgoingRequestEndedSpanData', () => {
17+
it('sets ip_tcp transport for HTTP/1.1', () => {
18+
const attributes = _getOutgoingRequestEndedSpanData(createResponse({ httpVersion: '1.1' }));
19+
20+
expect(attributes['network.transport']).toBe('ip_tcp');
21+
expect(attributes['net.transport']).toBe('ip_tcp');
22+
expect(attributes['network.protocol.version']).toBe('1.1');
23+
expect(attributes['http.flavor']).toBe('1.1');
24+
});
25+
26+
it('sets ip_udp transport for QUIC', () => {
27+
const attributes = _getOutgoingRequestEndedSpanData(createResponse({ httpVersion: 'QUIC' }));
28+
29+
expect(attributes['network.transport']).toBe('ip_udp');
30+
expect(attributes['net.transport']).toBe('ip_udp');
31+
});
32+
33+
it('does not throw when httpVersion is null', () => {
34+
expect(() =>
35+
_getOutgoingRequestEndedSpanData(createResponse({ httpVersion: null as unknown as string })),
36+
).not.toThrow();
37+
38+
const attributes = _getOutgoingRequestEndedSpanData(createResponse({ httpVersion: null as unknown as string }));
39+
expect(attributes['network.transport']).toBe('ip_tcp');
40+
expect(attributes['net.transport']).toBe('ip_tcp');
41+
});
42+
43+
it('does not throw when httpVersion is undefined', () => {
44+
expect(() =>
45+
_getOutgoingRequestEndedSpanData(createResponse({ httpVersion: undefined as unknown as string })),
46+
).not.toThrow();
47+
});
48+
});

0 commit comments

Comments
 (0)