1
1
import { getCurrentHub } from '@sentry/core' ;
2
2
import { Integration , Span , Transaction } from '@sentry/types' ;
3
- import { fill , parseSemver } from '@sentry/utils' ;
3
+ import { fill , logger , parseSemver } from '@sentry/utils' ;
4
4
import * as http from 'http' ;
5
5
import * as https from 'https' ;
6
6
7
+ import {
8
+ cleanSpanDescription ,
9
+ extractUrl ,
10
+ isSentryRequest ,
11
+ normalizeRequestArgs ,
12
+ RequestMethod ,
13
+ RequestMethodArgs ,
14
+ } from './utils/http' ;
15
+
7
16
const NODE_VERSION = parseSemver ( process . versions . node ) ;
8
17
9
18
/** http module integration */
@@ -45,7 +54,7 @@ export class Http implements Integration {
45
54
return ;
46
55
}
47
56
48
- const wrappedHandlerMaker = _createWrappedHandlerMaker ( this . _breadcrumbs , this . _tracing ) ;
57
+ const wrappedHandlerMaker = _createWrappedRequestMethodFactory ( this . _breadcrumbs , this . _tracing ) ;
49
58
50
59
const httpModule = require ( 'http' ) ;
51
60
fill ( httpModule , 'get' , wrappedHandlerMaker ) ;
@@ -62,9 +71,10 @@ export class Http implements Integration {
62
71
}
63
72
}
64
73
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 ;
68
78
69
79
/**
70
80
* 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;
75
85
*
76
86
* @returns A function which accepts the exiting handler and returns a wrapped handler
77
87
*/
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
87
102
if ( isSentryRequest ( requestUrl ) ) {
88
- return originalHandler . apply ( this , arguments ) ;
103
+ return originalRequestMethod . apply ( httpModule , requestArgs ) ;
89
104
}
90
105
91
106
let span : Span | undefined ;
@@ -96,32 +111,43 @@ function _createWrappedHandlerMaker(breadcrumbsEnabled: boolean, tracingEnabled:
96
111
transaction = scope . getTransaction ( ) ;
97
112
if ( transaction ) {
98
113
span = transaction . startChild ( {
99
- description : `${ typeof options === 'string' || ! options . method ? 'GET' : options . method } ${ requestUrl } ` ,
114
+ description : `${ requestOptions . method || 'GET' } ${ requestUrl } ` ,
100
115
op : 'request' ,
101
116
} ) ;
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 } ;
102
121
}
103
122
}
104
123
105
124
// 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 ;
109
130
if ( breadcrumbsEnabled ) {
110
- addRequestBreadcrumb ( 'response' , requestUrl , this , res ) ;
131
+ addRequestBreadcrumb ( 'response' , requestUrl , req , res ) ;
111
132
}
112
133
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 ) ;
115
138
span . finish ( ) ;
116
139
}
117
140
} )
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
+
119
145
if ( breadcrumbsEnabled ) {
120
- addRequestBreadcrumb ( 'error' , requestUrl , this ) ;
146
+ addRequestBreadcrumb ( 'error' , requestUrl , req ) ;
121
147
}
122
148
if ( tracingEnabled && span ) {
123
149
span . setHttpStatus ( 500 ) ;
124
- cleanDescription ( options , this , span ) ;
150
+ span . description = cleanSpanDescription ( span . description , requestOptions , req ) ;
125
151
span . finish ( ) ;
126
152
}
127
153
} ) ;
@@ -132,7 +158,7 @@ function _createWrappedHandlerMaker(breadcrumbsEnabled: boolean, tracingEnabled:
132
158
/**
133
159
* Captures Breadcrumb based on provided request/response pair
134
160
*/
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 {
136
162
if ( ! getCurrentHub ( ) . getIntegration ( Http ) ) {
137
163
return ;
138
164
}
@@ -154,72 +180,3 @@ function addRequestBreadcrumb(event: string, url: string, req: http.IncomingMess
154
180
} ,
155
181
) ;
156
182
}
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