@@ -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' ;
@@ -116,11 +116,13 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024;
116116 */
117117export class SentryHttpInstrumentation extends InstrumentationBase < SentryHttpInstrumentationOptions > {
118118 private _propagationDecisionMap : LRUMap < string , boolean > ;
119+ private _ignoreOutgoingRequestsMap : WeakMap < http . ClientRequest , boolean > ;
119120
120121 public constructor ( config : SentryHttpInstrumentationOptions = { } ) {
121122 super ( INSTRUMENTATION_NAME , VERSION , config ) ;
122123
123124 this . _propagationDecisionMap = new LRUMap < string , boolean > ( 100 ) ;
125+ this . _ignoreOutgoingRequestsMap = new WeakMap < http . ClientRequest , boolean > ( ) ;
124126 }
125127
126128 /** @inheritdoc */
@@ -149,6 +151,37 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
149151 this . _onOutgoingRequestCreated ( data . request ) ;
150152 } ) satisfies ChannelListener ;
151153
154+ const wrap = < T extends Http | Https > ( moduleExports : T ) : T => {
155+ if ( hasRegisteredHandlers ) {
156+ return moduleExports ;
157+ }
158+
159+ hasRegisteredHandlers = true ;
160+
161+ subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
162+ subscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
163+
164+ // When an error happens, we still want to have a breadcrumb
165+ // In this case, `http.client.response.finish` is not triggered
166+ subscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
167+
168+ // NOTE: This channel only exist since Node 22
169+ // Before that, outgoing requests are not patched
170+ // and trace headers are not propagated, sadly.
171+ if ( this . getConfig ( ) . propagateTraceInOutgoingRequests ) {
172+ subscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
173+ }
174+
175+ return moduleExports ;
176+ } ;
177+
178+ const unwrap = ( ) : void => {
179+ unsubscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
180+ unsubscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
181+ unsubscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
182+ unsubscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
183+ } ;
184+
152185 /**
153186 * You may be wondering why we register these diagnostics-channel listeners
154187 * in such a convoluted way (as InstrumentationNodeModuleDefinition...)˝,
@@ -158,64 +191,8 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
158191 * especially the "import-on-top" pattern of setting up ESM applications.
159192 */
160193 return [
161- new InstrumentationNodeModuleDefinition (
162- 'http' ,
163- [ '*' ] ,
164- ( moduleExports : Http ) : Http => {
165- if ( hasRegisteredHandlers ) {
166- return moduleExports ;
167- }
168-
169- hasRegisteredHandlers = true ;
170-
171- subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
172- subscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
173-
174- // When an error happens, we still want to have a breadcrumb
175- // In this case, `http.client.response.finish` is not triggered
176- subscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
177-
178- // NOTE: This channel only exist since Node 23
179- // Before that, outgoing requests are not patched
180- // and trace headers are not propagated, sadly.
181- if ( this . getConfig ( ) . propagateTraceInOutgoingRequests ) {
182- subscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
183- }
184-
185- return moduleExports ;
186- } ,
187- ( ) => {
188- unsubscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
189- unsubscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
190- unsubscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
191- unsubscribe ( 'http.client.request.created' , onHttpClientRequestCreated ) ;
192- } ,
193- ) ,
194- new InstrumentationNodeModuleDefinition (
195- 'https' ,
196- [ '*' ] ,
197- ( moduleExports : Https ) : Https => {
198- if ( hasRegisteredHandlers ) {
199- return moduleExports ;
200- }
201-
202- hasRegisteredHandlers = true ;
203-
204- subscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
205- subscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
206-
207- // When an error happens, we still want to have a breadcrumb
208- // In this case, `http.client.response.finish` is not triggered
209- subscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
210-
211- return moduleExports ;
212- } ,
213- ( ) => {
214- unsubscribe ( 'http.server.request.start' , onHttpServerRequestStart ) ;
215- unsubscribe ( 'http.client.response.finish' , onHttpClientResponseFinish ) ;
216- unsubscribe ( 'http.client.request.error' , onHttpClientRequestError ) ;
217- } ,
218- ) ,
194+ new InstrumentationNodeModuleDefinition ( 'http' , [ '*' ] , wrap , unwrap ) ,
195+ new InstrumentationNodeModuleDefinition ( 'https' , [ '*' ] , wrap , unwrap ) ,
219196 ] ;
220197 }
221198
@@ -228,13 +205,12 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
228205
229206 const _breadcrumbs = this . getConfig ( ) . breadcrumbs ;
230207 const breadCrumbsEnabled = typeof _breadcrumbs === 'undefined' ? true : _breadcrumbs ;
231- const options = getRequestOptions ( request ) ;
232208
233- const _ignoreOutgoingRequests = this . getConfig ( ) . ignoreOutgoingRequests ;
234- const shouldCreateBreadcrumb =
235- typeof _ignoreOutgoingRequests === 'function' ? ! _ignoreOutgoingRequests ( getRequestUrl ( request ) , options ) : true ;
209+ // Note: We cannot rely on the map being set by `_onOutgoingRequestCreated`, because that is not run in Node <22
210+ const shouldIgnore = this . _ignoreOutgoingRequestsMap . get ( request ) ?? this . _shouldIgnoreOutgoingRequest ( request ) ;
211+ this . _ignoreOutgoingRequestsMap . set ( request , shouldIgnore ) ;
236212
237- if ( breadCrumbsEnabled && shouldCreateBreadcrumb ) {
213+ if ( breadCrumbsEnabled && ! shouldIgnore ) {
238214 addRequestBreadcrumb ( request , response ) ;
239215 }
240216 }
@@ -244,15 +220,16 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
244220 * It has access to the request object, and can mutate it before the request is sent.
245221 */
246222 private _onOutgoingRequestCreated ( request : http . ClientRequest ) : void {
247- const url = getRequestUrl ( request ) ;
248- const ignoreOutgoingRequests = this . getConfig ( ) . ignoreOutgoingRequests ;
249- const shouldPropagate =
250- typeof ignoreOutgoingRequests === 'function' ? ! ignoreOutgoingRequests ( url , getRequestOptions ( request ) ) : true ;
223+ const shouldIgnore = this . _ignoreOutgoingRequestsMap . get ( request ) ?? this . _shouldIgnoreOutgoingRequest ( request ) ;
224+ this . _ignoreOutgoingRequestsMap . set ( request , shouldIgnore ) ;
251225
252- if ( ! shouldPropagate ) {
226+ if ( shouldIgnore ) {
253227 return ;
254228 }
255229
230+ // Add trace propagation headers
231+ const url = getRequestUrl ( request ) ;
232+
256233 // Manually add the trace headers, if it applies
257234 // Note: We do not use `propagation.inject()` here, because our propagator relies on an active span
258235 // Which we do not have in this case
@@ -368,6 +345,25 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
368345
369346 server . emit = newEmit ;
370347 }
348+
349+ /**
350+ * Check if the given outgoing request should be ignored.
351+ */
352+ private _shouldIgnoreOutgoingRequest ( request : http . ClientRequest ) : boolean {
353+ if ( isTracingSuppressed ( context . active ( ) ) ) {
354+ return true ;
355+ }
356+
357+ const ignoreOutgoingRequests = this . getConfig ( ) . ignoreOutgoingRequests ;
358+
359+ if ( ! ignoreOutgoingRequests ) {
360+ return false ;
361+ }
362+
363+ const options = getRequestOptions ( request ) ;
364+ const url = getRequestUrl ( request ) ;
365+ return ignoreOutgoingRequests ( url , options ) ;
366+ }
371367}
372368
373369/** Add a breadcrumb for outgoing requests. */
0 commit comments