11import { getCurrentHub } from '@sentry/core' ;
22import { Integration , Span , Transaction } from '@sentry/types' ;
3- import { fill , parseSemver } from '@sentry/utils' ;
3+ import { fill , logger , parseSemver } from '@sentry/utils' ;
44import * as http from 'http' ;
55import * as https from 'https' ;
66
7+ import {
8+ cleanSpanDescription ,
9+ extractUrl ,
10+ isSentryRequest ,
11+ normalizeRequestArgs ,
12+ RequestMethod ,
13+ RequestMethodArgs ,
14+ } from './utils/http' ;
15+
716const NODE_VERSION = parseSemver ( process . versions . node ) ;
817
918/** http module integration */
@@ -45,7 +54,7 @@ export class Http implements Integration {
4554 return ;
4655 }
4756
48- const wrappedHandlerMaker = _createWrappedHandlerMaker ( this . _breadcrumbs , this . _tracing ) ;
57+ const wrappedHandlerMaker = _createWrappedRequestMethodFactory ( this . _breadcrumbs , this . _tracing ) ;
4958
5059 const httpModule = require ( 'http' ) ;
5160 fill ( httpModule , 'get' , wrappedHandlerMaker ) ;
@@ -62,9 +71,10 @@ export class Http implements Integration {
6271 }
6372}
6473
65- type OriginalHandler = ( ) => http . ClientRequest ;
66- type WrappedHandler = ( options : string | http . ClientRequestArgs ) => http . ClientRequest ;
67- type WrappedHandlerMaker = ( originalHandler : OriginalHandler ) => WrappedHandler ;
74+ // for ease of reading below
75+ type OriginalRequestMethod = RequestMethod ;
76+ type WrappedRequestMethod = RequestMethod ;
77+ type WrappedRequestMethodFactory = ( original : OriginalRequestMethod ) => WrappedRequestMethod ;
6878
6979/**
7080 * Function which creates a function which creates wrapped versions of internal `request` and `get` calls within `http`
@@ -75,17 +85,22 @@ type WrappedHandlerMaker = (originalHandler: OriginalHandler) => WrappedHandler;
7585 *
7686 * @returns A function which accepts the exiting handler and returns a wrapped handler
7787 */
78- function _createWrappedHandlerMaker ( breadcrumbsEnabled : boolean , tracingEnabled : boolean ) : WrappedHandlerMaker {
79- return function wrappedHandlerMaker ( originalHandler : OriginalHandler ) : WrappedHandler {
80- return function wrappedHandler (
81- this : typeof http | typeof https ,
82- options : string | http . ClientRequestArgs ,
83- ) : http . ClientRequest {
84- const requestUrl = extractUrl ( options ) ;
85-
86- // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original handler
88+ function _createWrappedRequestMethodFactory (
89+ breadcrumbsEnabled : boolean ,
90+ tracingEnabled : boolean ,
91+ ) : WrappedRequestMethodFactory {
92+ return function wrappedRequestMethodFactory ( originalRequestMethod : OriginalRequestMethod ) : WrappedRequestMethod {
93+ return function wrappedMethod ( this : typeof http | typeof https , ...args : RequestMethodArgs ) : http . ClientRequest {
94+ // eslint-disable-next-line @typescript-eslint/no-this-alias
95+ const httpModule = this ;
96+
97+ const requestArgs = normalizeRequestArgs ( args ) ;
98+ const requestOptions = requestArgs [ 0 ] ;
99+ const requestUrl = extractUrl ( requestOptions ) ;
100+
101+ // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method
87102 if ( isSentryRequest ( requestUrl ) ) {
88- return originalHandler . apply ( this , arguments ) ;
103+ return originalRequestMethod . apply ( httpModule , requestArgs ) ;
89104 }
90105
91106 let span : Span | undefined ;
@@ -96,32 +111,43 @@ function _createWrappedHandlerMaker(breadcrumbsEnabled: boolean, tracingEnabled:
96111 transaction = scope . getTransaction ( ) ;
97112 if ( transaction ) {
98113 span = transaction . startChild ( {
99- description : `${ typeof options === 'string' || ! options . method ? 'GET' : options . method } ${ requestUrl } ` ,
114+ description : `${ requestOptions . method || 'GET' } ${ requestUrl } ` ,
100115 op : 'request' ,
101116 } ) ;
117+
118+ const sentryTraceHeader = span . toTraceparent ( ) ;
119+ logger . log ( `[Tracing] Adding sentry-trace header to outgoing request: ${ sentryTraceHeader } ` ) ;
120+ requestOptions . headers = { ...requestOptions . headers , 'sentry-trace' : sentryTraceHeader } ;
102121 }
103122 }
104123
105124 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
106- return originalHandler
107- . apply ( this , arguments )
108- . once ( 'response' , function ( this : http . IncomingMessage , res : http . ServerResponse ) : void {
125+ return originalRequestMethod
126+ . apply ( httpModule , requestArgs )
127+ . once ( 'response' , function ( this : http . ClientRequest , res : http . IncomingMessage ) : void {
128+ // eslint-disable-next-line @typescript-eslint/no-this-alias
129+ const req = this ;
109130 if ( breadcrumbsEnabled ) {
110- addRequestBreadcrumb ( 'response' , requestUrl , this , res ) ;
131+ addRequestBreadcrumb ( 'response' , requestUrl , req , res ) ;
111132 }
112133 if ( tracingEnabled && span ) {
113- span . setHttpStatus ( res . statusCode ) ;
114- cleanDescription ( options , this , span ) ;
134+ if ( res . statusCode ) {
135+ span . setHttpStatus ( res . statusCode ) ;
136+ }
137+ span . description = cleanSpanDescription ( span . description , requestOptions , req ) ;
115138 span . finish ( ) ;
116139 }
117140 } )
118- . once ( 'error' , function ( this : http . IncomingMessage ) : void {
141+ . once ( 'error' , function ( this : http . ClientRequest ) : void {
142+ // eslint-disable-next-line @typescript-eslint/no-this-alias
143+ const req = this ;
144+
119145 if ( breadcrumbsEnabled ) {
120- addRequestBreadcrumb ( 'error' , requestUrl , this ) ;
146+ addRequestBreadcrumb ( 'error' , requestUrl , req ) ;
121147 }
122148 if ( tracingEnabled && span ) {
123149 span . setHttpStatus ( 500 ) ;
124- cleanDescription ( options , this , span ) ;
150+ span . description = cleanSpanDescription ( span . description , requestOptions , req ) ;
125151 span . finish ( ) ;
126152 }
127153 } ) ;
@@ -132,7 +158,7 @@ function _createWrappedHandlerMaker(breadcrumbsEnabled: boolean, tracingEnabled:
132158/**
133159 * Captures Breadcrumb based on provided request/response pair
134160 */
135- function addRequestBreadcrumb ( event : string , url : string , req : http . IncomingMessage , res ?: http . ServerResponse ) : void {
161+ function addRequestBreadcrumb ( event : string , url : string , req : http . ClientRequest , res ?: http . IncomingMessage ) : void {
136162 if ( ! getCurrentHub ( ) . getIntegration ( Http ) ) {
137163 return ;
138164 }
@@ -154,72 +180,3 @@ function addRequestBreadcrumb(event: string, url: string, req: http.IncomingMess
154180 } ,
155181 ) ;
156182}
157-
158- /**
159- * Assemble a URL to be used for breadcrumbs and spans.
160- *
161- * @param url URL string or object containing the component parts
162- * @returns Fully-formed URL
163- */
164- export function extractUrl ( url : string | http . ClientRequestArgs ) : string {
165- if ( typeof url === 'string' ) {
166- return url ;
167- }
168- const protocol = url . protocol || '' ;
169- const hostname = url . hostname || url . host || '' ;
170- // Don't log standard :80 (http) and :443 (https) ports to reduce the noise
171- const port = ! url . port || url . port === 80 || url . port === 443 ? '' : `:${ url . port } ` ;
172- const path = url . path ? url . path : '/' ;
173-
174- // internal routes end up with too many slashes
175- return `${ protocol } //${ hostname } ${ port } ${ path } ` . replace ( '///' , '/' ) ;
176- }
177-
178- /**
179- * Handle an edge case with urls in the span description. Runs just before the span closes because it relies on
180- * data from the response object.
181- *
182- * @param requestOptions Configuration data for the request
183- * @param response Response object
184- * @param span Span representing the request
185- */
186- function cleanDescription (
187- requestOptions : string | http . ClientRequestArgs ,
188- response : http . IncomingMessage ,
189- span : Span ,
190- ) : void {
191- // There are some libraries which don't pass the request protocol in the options object, so attempt to retrieve it
192- // from the response and run the URL processing again. We only do this in the presence of a (non-empty) host value,
193- // because if we're missing both, it's likely we're dealing with an internal route, in which case we don't want to be
194- // jamming a random `http:` on the front of it.
195- if ( typeof requestOptions !== 'string' && ! Object . keys ( requestOptions ) . includes ( 'protocol' ) && requestOptions . host ) {
196- // Neither http.IncomingMessage nor any of its ancestors have an `agent` property in their type definitions, and
197- // http.Agent doesn't have a `protocol` property in its type definition. Nonetheless, at least one request library
198- // (superagent) arranges things that way, so might as well give it a shot.
199- try {
200- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
201- requestOptions . protocol = ( response as any ) . agent . protocol ;
202- span . description = `${ requestOptions . method || 'GET' } ${ extractUrl ( requestOptions ) } ` ;
203- } catch ( error ) {
204- // well, we tried
205- }
206- }
207- }
208-
209- /**
210- * Checks whether given url points to Sentry server
211- * @param url url to verify
212- */
213- function isSentryRequest ( url : string ) : boolean {
214- const client = getCurrentHub ( ) . getClient ( ) ;
215- if ( ! url || ! client ) {
216- return false ;
217- }
218-
219- const dsn = client . getDsn ( ) ;
220- if ( ! dsn ) {
221- return false ;
222- }
223-
224- return url . indexOf ( dsn . host ) !== - 1 ;
225- }
0 commit comments