Skip to content

Commit 24f03e6

Browse files
committed
fixup! fixup! fixup! feat(http): portable node:http client instrumentation (#20393)
1 parent 838bac3 commit 24f03e6

2 files changed

Lines changed: 114 additions & 5 deletions

File tree

packages/core/src/integrations/http/client-subscriptions.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,9 @@ export function getHttpClientSubscriptions(options: HttpInstrumentationOptions):
8080
});
8181
}
8282

83-
// Skip spans and trace propagation if tracing is suppressed
84-
// (e.g., inside Sentry.suppressTracing())
83+
// Skip all instrumentation if tracing is suppressed
84+
// (e.g., Sentry's own transport uses this to avoid self-instrumentation)
8585
if (getCurrentScope().getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] === true) {
86-
if (breadcrumbs) {
87-
breadcrumbsOnly(request);
88-
}
8986
return;
9087
}
9188

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { afterEach, describe, expect, it, vi } from 'vitest';
2+
import * as breadcrumbModule from '../../../../src/integrations/http/add-outgoing-request-breadcrumb';
3+
import { HTTP_ON_CLIENT_REQUEST } from '../../../../src/integrations/http/constants';
4+
import { getHttpClientSubscriptions } from '../../../../src/integrations/http/client-subscriptions';
5+
import type { HttpClientRequest, HttpIncomingMessage } from '../../../../src/integrations/http/types';
6+
import { SUPPRESS_TRACING_KEY } from '../../../../src/tracing';
7+
import { getCurrentScope, withScope } from '../../../../src/currentScopes';
8+
9+
function makeMockRequest(): HttpClientRequest & {
10+
_responseListeners: ((res: HttpIncomingMessage) => void)[];
11+
} {
12+
const responseListeners: ((res: HttpIncomingMessage) => void)[] = [];
13+
return {
14+
method: 'GET',
15+
path: '/test',
16+
host: 'example.com',
17+
protocol: 'http:',
18+
port: 80,
19+
getHeader: () => undefined,
20+
getHeaders: () => ({}),
21+
setHeader: vi.fn(),
22+
removeHeader: vi.fn(),
23+
end: vi.fn(),
24+
on: vi.fn(),
25+
once: vi.fn(),
26+
prependListener: vi.fn((_event: string, fn: (...args: unknown[]) => void) => {
27+
responseListeners.push(fn as (res: HttpIncomingMessage) => void);
28+
}),
29+
listenerCount: () => 0,
30+
removeListener: vi.fn(),
31+
_responseListeners: responseListeners,
32+
} as unknown as HttpClientRequest & { _responseListeners: ((res: HttpIncomingMessage) => void)[] };
33+
}
34+
35+
function makeMockResponse(): HttpIncomingMessage & { _endListeners: (() => void)[] } {
36+
const endListeners: (() => void)[] = [];
37+
return {
38+
statusCode: 200,
39+
statusMessage: 'OK',
40+
httpVersion: '1.1',
41+
headers: {},
42+
resume: vi.fn(),
43+
on: vi.fn((_event: string, fn: (...args: unknown[]) => void) => {
44+
if (_event === 'end') endListeners.push(fn as () => void);
45+
}),
46+
addListener: vi.fn(),
47+
off: vi.fn(),
48+
removeListener: vi.fn(),
49+
_endListeners: endListeners,
50+
} as unknown as HttpIncomingMessage & { _endListeners: (() => void)[] };
51+
}
52+
53+
describe('getHttpClientSubscriptions', () => {
54+
afterEach(() => {
55+
vi.restoreAllMocks();
56+
getCurrentScope().setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: undefined });
57+
});
58+
59+
describe('suppressTracing', () => {
60+
it('does not add breadcrumbs when suppressTracing is active', () => {
61+
const spy = vi.spyOn(breadcrumbModule, 'addOutgoingRequestBreadcrumb');
62+
const subscriptions = getHttpClientSubscriptions({ breadcrumbs: true, spans: false });
63+
const handler = subscriptions[HTTP_ON_CLIENT_REQUEST];
64+
65+
withScope(scope => {
66+
scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true });
67+
68+
const request = makeMockRequest();
69+
handler({ request }, HTTP_ON_CLIENT_REQUEST);
70+
71+
// no response listeners should have been registered
72+
expect(request._responseListeners).toHaveLength(0);
73+
74+
// simulate a response completing anyway — breadcrumb must still not fire
75+
const response = makeMockResponse();
76+
request._responseListeners.forEach(fn => fn(response));
77+
response._endListeners.forEach(fn => fn());
78+
});
79+
80+
expect(spy).not.toHaveBeenCalled();
81+
});
82+
83+
it('does not propagate trace headers when suppressTracing is active', () => {
84+
const subscriptions = getHttpClientSubscriptions({ breadcrumbs: false, spans: false, propagateTrace: true });
85+
const handler = subscriptions[HTTP_ON_CLIENT_REQUEST];
86+
87+
withScope(scope => {
88+
scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true });
89+
90+
const request = makeMockRequest();
91+
handler({ request }, HTTP_ON_CLIENT_REQUEST);
92+
93+
expect(request.setHeader).not.toHaveBeenCalled();
94+
});
95+
});
96+
97+
it('still adds breadcrumbs when suppressTracing is NOT active', () => {
98+
const spy = vi.spyOn(breadcrumbModule, 'addOutgoingRequestBreadcrumb');
99+
const subscriptions = getHttpClientSubscriptions({ breadcrumbs: true, spans: false });
100+
const handler = subscriptions[HTTP_ON_CLIENT_REQUEST];
101+
102+
const request = makeMockRequest();
103+
handler({ request }, HTTP_ON_CLIENT_REQUEST);
104+
105+
const response = makeMockResponse();
106+
request._responseListeners.forEach(fn => fn(response));
107+
response._endListeners.forEach(fn => fn());
108+
109+
expect(spy).toHaveBeenCalledOnce();
110+
});
111+
});
112+
});

0 commit comments

Comments
 (0)