@@ -5,7 +5,7 @@ import type * as http from 'node:http';
55import type * as https from 'node:https' ;
66import type { EventEmitter } from 'node:stream' ;
77import { context , propagation } from '@opentelemetry/api' ;
8- import { VERSION } from '@opentelemetry/core' ;
8+ import { isTracingSuppressed , VERSION } from '@opentelemetry/core' ;
99import type { InstrumentationConfig } from '@opentelemetry/instrumentation' ;
1010import { InstrumentationBase , InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation' ;
1111import type { AggregationCounts , Client , SanitizedRequestData , Scope } from '@sentry/core' ;
@@ -132,11 +132,13 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024;
132132 */
133133export class SentryHttpInstrumentation extends InstrumentationBase < SentryHttpInstrumentationOptions > {
134134 private _propagationDecisionMap : LRUMap < string , boolean > ;
135+ private _ignoreOutgoingRequestsMap : WeakMap < http . ClientRequest , boolean > ;
135136
136137 public constructor ( config : SentryHttpInstrumentationOptions = { } ) {
137138 super ( INSTRUMENTATION_NAME , VERSION , config ) ;
138139
139140 this . _propagationDecisionMap = new LRUMap < string , boolean > ( 100 ) ;
141+ this . _ignoreOutgoingRequestsMap = new WeakMap < http . ClientRequest , boolean > ( ) ;
140142 }
141143
142144 /** @inheritdoc */
@@ -165,6 +167,37 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
165167 this . _onOutgoingRequestCreated ( data . request ) ;
166168 } ) satisfies ChannelListener ;
167169
170+ const wrap = < T extends Http | Https > ( moduleExports : T ) : T => {
171+ if ( hasRegisteredHandlers ) {
172+ return moduleExports ;
173+ }
174+
175+ hasRegisteredHandlers = true ;
176+
177+ subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
178+ subscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
179+
180+ // When an error happens, we still want to have a breadcrumb
181+ // In this case, `http.client.response.finish` is not triggered
182+ subscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
183+
184+ // NOTE: This channel only exist since Node 22
185+ // Before that, outgoing requests are not patched
186+ // and trace headers are not propagated, sadly.
187+ if ( this . getConfig ( ) . propagateTraceInOutgoingRequests ) {
188+ subscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
189+ }
190+
191+ return moduleExports ;
192+ } ;
193+
194+ const unwrap = ( ) : void => {
195+ unsubscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
196+ unsubscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
197+ unsubscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
198+ unsubscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
199+ } ;
200+
168201 /**
169202 * You may be wondering why we register these diagnostics-channel listeners
170203 * in such a convoluted way (as InstrumentationNodeModuleDefinition...)˝,
@@ -174,64 +207,8 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
174207 * especially the "import-on-top" pattern of setting up ESM applications.
175208 */
176209 return [
177- new InstrumentationNodeModuleDefinition (
178- 'http' ,
179- [ '*' ] ,
180- ( moduleExports : Http ) : Http => {
181- if ( hasRegisteredHandlers ) {
182- return moduleExports ;
183- }
184-
185- hasRegisteredHandlers = true ;
186-
187- subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
188- subscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
189-
190- // When an error happens, we still want to have a breadcrumb
191- // In this case, `http.client.response.finish` is not triggered
192- subscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
193-
194- // NOTE: This channel only exist since Node 23
195- // Before that, outgoing requests are not patched
196- // and trace headers are not propagated, sadly.
197- if ( this . getConfig ( ) . propagateTraceInOutgoingRequests ) {
198- subscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
199- }
200-
201- return moduleExports ;
202- } ,
203- ( ) => {
204- unsubscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
205- unsubscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
206- unsubscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
207- unsubscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
208- } ,
209- ) ,
210- new InstrumentationNodeModuleDefinition (
211- 'https' ,
212- [ '*' ] ,
213- ( moduleExports : Https ) : Https => {
214- if ( hasRegisteredHandlers ) {
215- return moduleExports ;
216- }
217-
218- hasRegisteredHandlers = true ;
219-
220- subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
221- subscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
222-
223- // When an error happens, we still want to have a breadcrumb
224- // In this case, `http.client.response.finish` is not triggered
225- subscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
226-
227- return moduleExports ;
228- } ,
229- ( ) => {
230- unsubscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
231- unsubscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
232- unsubscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
233- } ,
234- ) ,
210+ new InstrumentationNodeModuleDefinition ( 'http' , [ '*' ] , wrap , unwrap ) ,
211+ new InstrumentationNodeModuleDefinition ( 'https' , [ '*' ] , wrap , unwrap ) ,
235212 ] ;
236213 }
237214
@@ -244,13 +221,12 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
244221
245222 const _breadcrumbs = this . getConfig ( ) . breadcrumbs ;
246223 const breadCrumbsEnabled = typeof _breadcrumbs === 'undefined' ? true : _breadcrumbs ;
247- const options = getRequestOptions ( request ) ;
248224
249- const _ignoreOutgoingRequests = this . getConfig ( ) . ignoreOutgoingRequests ;
250- const shouldCreateBreadcrumb =
251- typeof _ignoreOutgoingRequests === 'function' ? ! _ignoreOutgoingRequests ( getRequestUrl ( request ) , options ) : true ;
225+ // Note: We cannot rely on the map being set by `_onOutgoingRequestCreated`, because that is not run in Node <22
226+ const shouldIgnore = this . _ignoreOutgoingRequestsMap . get ( request ) ?? this . _shouldIgnoreOutgoingRequest ( request ) ;
227+ this . _ignoreOutgoingRequestsMap . set ( request , shouldIgnore ) ;
252228
253- if ( breadCrumbsEnabled && shouldCreateBreadcrumb ) {
229+ if ( breadCrumbsEnabled && ! shouldIgnore ) {
254230 addRequestBreadcrumb ( request , response ) ;
255231 }
256232 }
@@ -260,15 +236,16 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
260236 * It has access to the request object, and can mutate it before the request is sent.
261237 */
262238 private _onOutgoingRequestCreated ( request : http . ClientRequest ) : void {
263- const url = getRequestUrl ( request ) ;
264- const ignoreOutgoingRequests = this . getConfig ( ) . ignoreOutgoingRequests ;
265- const shouldPropagate =
266- typeof ignoreOutgoingRequests === 'function' ? ! ignoreOutgoingRequests ( url , getRequestOptions ( request ) ) : true ;
239+ const shouldIgnore = this . _ignoreOutgoingRequestsMap . get ( request ) ?? this . _shouldIgnoreOutgoingRequest ( request ) ;
240+ this . _ignoreOutgoingRequestsMap . set ( request , shouldIgnore ) ;
267241
268- if ( ! shouldPropagate ) {
242+ if ( shouldIgnore ) {
269243 return ;
270244 }
271245
246+ // Add trace propagation headers
247+ const url = getRequestUrl ( request ) ;
248+
272249 // Manually add the trace headers, if it applies
273250 // Note: We do not use `propagation.inject()` here, because our propagator relies on an active span
274251 // Which we do not have in this case
@@ -384,6 +361,25 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
384361
385362 server . emit = newEmit ;
386363 }
364+
365+ /**
366+ * Check if the given outgoing request should be ignored.
367+ */
368+ private _shouldIgnoreOutgoingRequest ( request : http . ClientRequest ) : boolean {
369+ if ( isTracingSuppressed ( context . active ( ) ) ) {
370+ return true ;
371+ }
372+
373+ const ignoreOutgoingRequests = this . getConfig ( ) . ignoreOutgoingRequests ;
374+
375+ if ( ! ignoreOutgoingRequests ) {
376+ return false ;
377+ }
378+
379+ const options = getRequestOptions ( request ) ;
380+ const url = getRequestUrl ( request ) ;
381+ return ignoreOutgoingRequests ( url , options ) ;
382+ }
387383}
388384
389385/** Add a breadcrumb for outgoing requests. */
0 commit comments