@@ -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