1+ /* eslint-disable max-lines */
12import type * as http from 'node:http' ;
23import type { IncomingMessage , RequestOptions } from 'node:http' ;
34import type * as https from 'node:https' ;
45import type { EventEmitter } from 'node:stream' ;
5- /* eslint-disable max-lines */
66import { VERSION } from '@opentelemetry/core' ;
77import type { InstrumentationConfig } from '@opentelemetry/instrumentation' ;
88import { InstrumentationBase , InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation' ;
99import type { AggregationCounts , Client , RequestEventData , SanitizedRequestData , Scope } from '@sentry/core' ;
1010import {
11- LRUMap ,
1211 addBreadcrumb ,
1312 generateSpanId ,
1413 getBreadcrumbLogLevelFromHttpStatusCode ,
1514 getClient ,
1615 getIsolationScope ,
1716 getSanitizedUrlString ,
18- getTraceData ,
1917 httpRequestToRequestData ,
2018 logger ,
21- objectToBaggageHeader ,
22- parseBaggageHeader ,
2319 parseUrl ,
2420 stripUrlQueryAndFragment ,
2521 withIsolationScope ,
2622 withScope ,
2723} from '@sentry/core' ;
28- import { shouldPropagateTraceForUrl } from '@sentry/opentelemetry' ;
2924import { DEBUG_BUILD } from '../../debug-build' ;
3025import { getRequestUrl } from '../../utils/getRequestUrl' ;
3126import { getRequestInfo } from './vendor/getRequestInfo' ;
3227
3328type Http = typeof http ;
3429type Https = typeof https ;
3530
36- type RequestArgs =
37- // eslint-disable-next-line @typescript-eslint/ban-types
38- | [ url : string | URL , options ?: RequestOptions , callback ?: Function ]
39- // eslint-disable-next-line @typescript-eslint/ban-types
40- | [ options : RequestOptions , callback ?: Function ] ;
41-
4231type SentryHttpInstrumentationOptions = InstrumentationConfig & {
4332 /**
4433 * Whether breadcrumbs should be recorded for requests.
@@ -91,11 +80,8 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024;
9180 * https://github.com/open-telemetry/opentelemetry-js/blob/f8ab5592ddea5cba0a3b33bf8d74f27872c0367f/experimental/packages/opentelemetry-instrumentation-http/src/http.ts
9281 */
9382export class SentryHttpInstrumentation extends InstrumentationBase < SentryHttpInstrumentationOptions > {
94- private _propagationDecisionMap : LRUMap < string , boolean > ;
95-
9683 public constructor ( config : SentryHttpInstrumentationOptions = { } ) {
9784 super ( '@sentry/instrumentation-http' , VERSION , config ) ;
98- this . _propagationDecisionMap = new LRUMap < string , boolean > ( 100 ) ;
9985 }
10086
10187 /** @inheritdoc */
@@ -113,7 +99,7 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
11399 stealthWrap ( moduleExports . Server . prototype , 'emit' , this . _getPatchIncomingRequestFunction ( ) ) ;
114100
115101 // Patch outgoing requests for breadcrumbs
116- const patchedRequest = stealthWrap ( moduleExports , 'request' , this . _getPatchOutgoingRequestFunction ( 'http' ) ) ;
102+ const patchedRequest = stealthWrap ( moduleExports , 'request' , this . _getPatchOutgoingRequestFunction ( ) ) ;
117103 stealthWrap ( moduleExports , 'get' , this . _getPatchOutgoingGetFunction ( patchedRequest ) ) ;
118104
119105 return moduleExports ;
@@ -134,7 +120,7 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
134120 stealthWrap ( moduleExports . Server . prototype , 'emit' , this . _getPatchIncomingRequestFunction ( ) ) ;
135121
136122 // Patch outgoing requests for breadcrumbs
137- const patchedRequest = stealthWrap ( moduleExports , 'request' , this . _getPatchOutgoingRequestFunction ( 'https' ) ) ;
123+ const patchedRequest = stealthWrap ( moduleExports , 'request' , this . _getPatchOutgoingRequestFunction ( ) ) ;
138124 stealthWrap ( moduleExports , 'get' , this . _getPatchOutgoingGetFunction ( patchedRequest ) ) ;
139125
140126 return moduleExports ;
@@ -211,7 +197,7 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
211197 /**
212198 * Patch the outgoing request function for breadcrumbs.
213199 */
214- private _getPatchOutgoingRequestFunction ( component : 'http' | 'https' ) : (
200+ private _getPatchOutgoingRequestFunction ( ) : (
215201 // eslint-disable-next-line @typescript-eslint/no-explicit-any
216202 original : ( ...args : any [ ] ) => http . ClientRequest ,
217203 ) => ( options : URL | http . RequestOptions | string , ...args : unknown [ ] ) => http . ClientRequest {
@@ -222,32 +208,22 @@ export class SentryHttpInstrumentation extends InstrumentationBase<SentryHttpIns
222208 return function outgoingRequest ( this : unknown , ...args : unknown [ ] ) : http . ClientRequest {
223209 instrumentation . _diag . debug ( 'http instrumentation for outgoing requests' ) ;
224210
211+ // Making a copy to avoid mutating the original args array
225212 // We need to access and reconstruct the request options object passed to `ignoreOutgoingRequests`
226213 // so that it matches what Otel instrumentation passes to `ignoreOutgoingRequestHook`.
227214 // @see https://github.com/open-telemetry/opentelemetry-js/blob/7293e69c1e55ca62e15d0724d22605e61bd58952/experimental/packages/opentelemetry-instrumentation-http/src/http.ts#L756-L789
228- const requestArgs = [ ...args ] as RequestArgs ;
229-
230- let options = requestArgs [ 0 ] ;
215+ const argsCopy = [ ...args ] ;
231216
232- // Make sure correct fallback attributes are set on the options object for https before we pass them to the vendored getRequestInfo function.
233- // Ref: https://github.com/open-telemetry/opentelemetry-js/blob/887ff1cd6e3f795f703e40a9fbe89b3cba7e88c3/experimental/packages/opentelemetry-instrumentation-http/src/http.ts#L390
234- if ( component === 'https' && typeof options === 'object' && options ?. constructor ?. name !== 'URL' ) {
235- options = Object . assign ( { } , options ) ;
236- options . protocol = options . protocol || 'https:' ;
237- options . port = options . port || 443 ;
238- }
217+ const options = argsCopy . shift ( ) as URL | http . RequestOptions | string ;
239218
240- const extraOptions = typeof requestArgs [ 1 ] === 'object' ? requestArgs [ 1 ] : undefined ;
219+ const extraOptions =
220+ typeof argsCopy [ 0 ] === 'object' && ( typeof options === 'string' || options instanceof URL )
221+ ? ( argsCopy . shift ( ) as http . RequestOptions )
222+ : undefined ;
241223
242- const { optionsParsed, origin , pathname } = getRequestInfo ( instrumentation . _diag , options , extraOptions ) ;
224+ const { optionsParsed } = getRequestInfo ( instrumentation . _diag , options , extraOptions ) ;
243225
244- const url = getAbsoluteUrl ( origin , pathname ) ;
245-
246- addSentryHeadersToRequestOptions ( url , optionsParsed , instrumentation . _propagationDecisionMap ) ;
247-
248- const request = original . apply ( this , [ optionsParsed , ...requestArgs . slice ( 1 ) ] ) as ReturnType <
249- typeof http . request
250- > ;
226+ const request = original . apply ( this , args ) as ReturnType < typeof http . request > ;
251227
252228 request . prependListener ( 'response' , ( response : http . IncomingMessage ) => {
253229 const _breadcrumbs = instrumentation . getConfig ( ) . breadcrumbs ;
@@ -481,44 +457,6 @@ function patchRequestToCaptureBody(req: IncomingMessage, isolationScope: Scope):
481457 }
482458}
483459
484- /**
485- * Mutates the passed in `options` and adds `sentry-trace` / `baggage` headers, if they are not already set.
486- */
487- function addSentryHeadersToRequestOptions (
488- url : string ,
489- options : RequestOptions ,
490- propagationDecisionMap : LRUMap < string , boolean > ,
491- ) : void {
492- // Manually add the trace headers, if it applies
493- // Note: We do not use `propagation.inject()` here, because our propagator relies on an active span
494- // Which we do not have in this case
495- const tracePropagationTargets = getClient ( ) ?. getOptions ( ) . tracePropagationTargets ;
496- const addedHeaders = shouldPropagateTraceForUrl ( url , tracePropagationTargets , propagationDecisionMap )
497- ? getTraceData ( )
498- : undefined ;
499-
500- if ( ! addedHeaders ) {
501- return ;
502- }
503-
504- if ( ! options . headers ) {
505- options . headers = { } ;
506- }
507- const headers = options . headers ;
508-
509- const { 'sentry-trace' : sentryTrace , baggage } = addedHeaders ;
510-
511- // We do not want to overwrite existing header here, if it was already set
512- if ( sentryTrace && ! headers [ 'sentry-trace' ] ) {
513- headers [ 'sentry-trace' ] = sentryTrace ;
514- }
515-
516- // For baggage, we make sure to merge this into a possibly existing header
517- if ( baggage ) {
518- headers [ 'baggage' ] = mergeBaggageHeaders ( headers [ 'baggage' ] , baggage ) ;
519- }
520- }
521-
522460/**
523461 * Starts a session and tracks it in the context of a given isolation scope.
524462 * When the passed response is finished, the session is put into a task and is
@@ -593,49 +531,3 @@ const clientToRequestSessionAggregatesMap = new Map<
593531 Client ,
594532 { [ timestampRoundedToSeconds : string ] : { exited : number ; crashed : number ; errored : number } }
595533> ( ) ;
596-
597- function getAbsoluteUrl ( origin : string , path : string = '/' ) : string {
598- try {
599- const url = new URL ( path , origin ) ;
600- return url . toString ( ) ;
601- } catch {
602- // fallback: Construct it on our own
603- const url = `${ origin } ` ;
604-
605- if ( url . endsWith ( '/' ) && path . startsWith ( '/' ) ) {
606- return `${ url } ${ path . slice ( 1 ) } ` ;
607- }
608-
609- if ( ! url . endsWith ( '/' ) && ! path . startsWith ( '/' ) ) {
610- return `${ url } /${ path . slice ( 1 ) } ` ;
611- }
612-
613- return `${ url } ${ path } ` ;
614- }
615- }
616-
617- function mergeBaggageHeaders (
618- existing : string | string [ ] | number | undefined ,
619- baggage : string ,
620- ) : string | string [ ] | number | undefined {
621- if ( ! existing ) {
622- return baggage ;
623- }
624-
625- const existingBaggageEntries = parseBaggageHeader ( existing ) ;
626- const newBaggageEntries = parseBaggageHeader ( baggage ) ;
627-
628- if ( ! newBaggageEntries ) {
629- return existing ;
630- }
631-
632- // Existing entries take precedence, ensuring order remains stable for minimal changes
633- const mergedBaggageEntries = { ...existingBaggageEntries } ;
634- Object . entries ( newBaggageEntries ) . forEach ( ( [ key , value ] ) => {
635- if ( ! mergedBaggageEntries [ key ] ) {
636- mergedBaggageEntries [ key ] = value ;
637- }
638- } ) ;
639-
640- return objectToBaggageHeader ( mergedBaggageEntries ) ;
641- }
0 commit comments