1
- /* eslint-disable max-lines */
2
1
/* eslint-disable @typescript-eslint/no-explicit-any */
3
2
import { captureException , getCurrentHub , startTransaction , withScope } from '@sentry/core' ;
4
- import { Event , ExtractedNodeRequestData , Span , Transaction } from '@sentry/types' ;
3
+ import { Event , Span } from '@sentry/types' ;
5
4
import {
5
+ AddRequestDataToEventOptions ,
6
+ addRequestDataToTransaction ,
7
+ extractPathForTransaction ,
6
8
extractTraceparentData ,
7
- isPlainObject ,
8
9
isString ,
9
10
logger ,
10
- normalize ,
11
11
parseBaggageString ,
12
- stripUrlQueryAndFragment ,
13
12
} from '@sentry/utils' ;
14
- import * as cookie from 'cookie' ;
15
13
import * as domain from 'domain' ;
16
14
import * as http from 'http' ;
17
- import * as url from 'url' ;
18
15
19
16
import { NodeClient } from './client' ;
20
- import { flush , isAutoSessionTrackingEnabled } from './sdk' ;
21
-
22
- export interface ExpressRequest {
23
- baseUrl ?: string ;
24
- connection ?: {
25
- remoteAddress ?: string ;
26
- } ;
27
- ip ?: string ;
28
- method ?: string ;
29
- originalUrl ?: string ;
30
- route ?: {
31
- path : string ;
32
- stack : [
33
- {
34
- name : string ;
35
- } ,
36
- ] ;
37
- } ;
38
- query ?: {
39
- // It can be: undefined | string | string[] | ParsedQs | ParsedQs[] (from `qs` package), but we dont want to pull it.
40
- [ key : string ] : unknown ;
41
- } ;
42
- url ?: string ;
43
- user ?: {
44
- [ key : string ] : any ;
45
- } ;
46
- }
17
+ import { addRequestDataToEvent , extractRequestData , flush , isAutoSessionTrackingEnabled } from './sdk' ;
47
18
48
19
/**
49
20
* Express-compatible tracing handler.
@@ -66,7 +37,7 @@ export function tracingHandler(): (
66
37
67
38
const transaction = startTransaction (
68
39
{
69
- name : extractExpressTransactionName ( req , { path : true , method : true } ) ,
40
+ name : extractPathForTransaction ( req , { path : true , method : true } ) ,
70
41
op : 'http.server' ,
71
42
...traceparentData ,
72
43
...( baggage && { metadata : { baggage : baggage } } ) ,
@@ -89,7 +60,7 @@ export function tracingHandler(): (
89
60
// Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction
90
61
// closes
91
62
setImmediate ( ( ) => {
92
- addExpressReqToTransaction ( transaction , req ) ;
63
+ addRequestDataToTransaction ( transaction , req ) ;
93
64
transaction . setHttpStatus ( res . statusCode ) ;
94
65
transaction . finish ( ) ;
95
66
} ) ;
@@ -99,263 +70,7 @@ export function tracingHandler(): (
99
70
} ;
100
71
}
101
72
102
- /**
103
- * Set parameterized as transaction name e.g.: `GET /users/:id`
104
- * Also adds more context data on the transaction from the request
105
- */
106
- function addExpressReqToTransaction ( transaction : Transaction | undefined , req : ExpressRequest ) : void {
107
- if ( ! transaction ) return ;
108
- transaction . name = extractExpressTransactionName ( req , { path : true , method : true } ) ;
109
- transaction . setData ( 'url' , req . originalUrl ) ;
110
- transaction . setData ( 'baseUrl' , req . baseUrl ) ;
111
- transaction . setData ( 'query' , req . query ) ;
112
- }
113
-
114
- /**
115
- * Extracts complete generalized path from the request object and uses it to construct transaction name.
116
- *
117
- * eg. GET /mountpoint/user/:id
118
- *
119
- * @param req The ExpressRequest object
120
- * @param options What to include in the transaction name (method, path, or both)
121
- *
122
- * @returns The fully constructed transaction name
123
- */
124
- function extractExpressTransactionName (
125
- req : ExpressRequest ,
126
- options : { path ?: boolean ; method ?: boolean } = { } ,
127
- ) : string {
128
- const method = req . method ?. toUpperCase ( ) ;
129
-
130
- let path = '' ;
131
- if ( req . route ) {
132
- path = `${ req . baseUrl || '' } ${ req . route . path } ` ;
133
- } else if ( req . originalUrl || req . url ) {
134
- path = stripUrlQueryAndFragment ( req . originalUrl || req . url || '' ) ;
135
- }
136
-
137
- let info = '' ;
138
- if ( options . method && method ) {
139
- info += method ;
140
- }
141
- if ( options . method && options . path ) {
142
- info += ' ' ;
143
- }
144
- if ( options . path && path ) {
145
- info += path ;
146
- }
147
-
148
- return info ;
149
- }
150
-
151
- type TransactionNamingScheme = 'path' | 'methodPath' | 'handler' ;
152
-
153
- /** JSDoc */
154
- function extractTransaction ( req : ExpressRequest , type : boolean | TransactionNamingScheme ) : string {
155
- switch ( type ) {
156
- case 'path' : {
157
- return extractExpressTransactionName ( req , { path : true } ) ;
158
- }
159
- case 'handler' : {
160
- return req . route ?. stack [ 0 ] . name || '<anonymous>' ;
161
- }
162
- case 'methodPath' :
163
- default : {
164
- return extractExpressTransactionName ( req , { path : true , method : true } ) ;
165
- }
166
- }
167
- }
168
-
169
- /** Default user keys that'll be used to extract data from the request */
170
- const DEFAULT_USER_KEYS = [ 'id' , 'username' , 'email' ] ;
171
-
172
- /** JSDoc */
173
- function extractUserData (
174
- user : {
175
- [ key : string ] : any ;
176
- } ,
177
- keys : boolean | string [ ] ,
178
- ) : { [ key : string ] : any } {
179
- const extractedUser : { [ key : string ] : any } = { } ;
180
- const attributes = Array . isArray ( keys ) ? keys : DEFAULT_USER_KEYS ;
181
-
182
- attributes . forEach ( key => {
183
- if ( user && key in user ) {
184
- extractedUser [ key ] = user [ key ] ;
185
- }
186
- } ) ;
187
-
188
- return extractedUser ;
189
- }
190
-
191
- /** Default request keys that'll be used to extract data from the request */
192
- const DEFAULT_REQUEST_KEYS = [ 'cookies' , 'data' , 'headers' , 'method' , 'query_string' , 'url' ] ;
193
-
194
- /**
195
- * Normalizes data from the request object, accounting for framework differences.
196
- *
197
- * @param req The request object from which to extract data
198
- * @param keys An optional array of keys to include in the normalized data. Defaults to DEFAULT_REQUEST_KEYS if not
199
- * provided.
200
- * @returns An object containing normalized request data
201
- */
202
- export function extractRequestData (
203
- req : { [ key : string ] : any } ,
204
- keys : string [ ] = DEFAULT_REQUEST_KEYS ,
205
- ) : ExtractedNodeRequestData {
206
- const requestData : { [ key : string ] : any } = { } ;
207
-
208
- // headers:
209
- // node, express, nextjs: req.headers
210
- // koa: req.header
211
- const headers = ( req . headers || req . header || { } ) as {
212
- host ?: string ;
213
- cookie ?: string ;
214
- } ;
215
- // method:
216
- // node, express, koa, nextjs: req.method
217
- const method = req . method ;
218
- // host:
219
- // express: req.hostname in > 4 and req.host in < 4
220
- // koa: req.host
221
- // node, nextjs: req.headers.host
222
- const host = req . hostname || req . host || headers . host || '<no host>' ;
223
- // protocol:
224
- // node, nextjs: <n/a>
225
- // express, koa: req.protocol
226
- const protocol =
227
- req . protocol === 'https' || req . secure || ( ( req . socket || { } ) as { encrypted ?: boolean } ) . encrypted
228
- ? 'https'
229
- : 'http' ;
230
- // url (including path and query string):
231
- // node, express: req.originalUrl
232
- // koa, nextjs: req.url
233
- const originalUrl = ( req . originalUrl || req . url || '' ) as string ;
234
- // absolute url
235
- const absoluteUrl = `${ protocol } ://${ host } ${ originalUrl } ` ;
236
-
237
- keys . forEach ( key => {
238
- switch ( key ) {
239
- case 'headers' :
240
- requestData . headers = headers ;
241
- break ;
242
- case 'method' :
243
- requestData . method = method ;
244
- break ;
245
- case 'url' :
246
- requestData . url = absoluteUrl ;
247
- break ;
248
- case 'cookies' :
249
- // cookies:
250
- // node, express, koa: req.headers.cookie
251
- // vercel, sails.js, express (w/ cookie middleware), nextjs: req.cookies
252
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
253
- requestData . cookies = req . cookies || cookie . parse ( headers . cookie || '' ) ;
254
- break ;
255
- case 'query_string' :
256
- // query string:
257
- // node: req.url (raw)
258
- // express, koa, nextjs: req.query
259
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
260
- requestData . query_string = req . query || url . parse ( originalUrl || '' , false ) . query ;
261
- break ;
262
- case 'data' :
263
- if ( method === 'GET' || method === 'HEAD' ) {
264
- break ;
265
- }
266
- // body data:
267
- // express, koa, nextjs: req.body
268
- //
269
- // when using node by itself, you have to read the incoming stream(see
270
- // https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
271
- // where they're going to store the final result, so they'll have to capture this data themselves
272
- if ( req . body !== undefined ) {
273
- requestData . data = isString ( req . body ) ? req . body : JSON . stringify ( normalize ( req . body ) ) ;
274
- }
275
- break ;
276
- default :
277
- if ( { } . hasOwnProperty . call ( req , key ) ) {
278
- requestData [ key ] = ( req as { [ key : string ] : any } ) [ key ] ;
279
- }
280
- }
281
- } ) ;
282
-
283
- return requestData ;
284
- }
285
-
286
- /**
287
- * Options deciding what parts of the request to use when enhancing an event
288
- */
289
- export interface ParseRequestOptions {
290
- ip ?: boolean ;
291
- request ?: boolean | string [ ] ;
292
- transaction ?: boolean | TransactionNamingScheme ;
293
- user ?: boolean | string [ ] ;
294
- }
295
-
296
- /**
297
- * Enriches passed event with request data.
298
- *
299
- * @param event Will be mutated and enriched with req data
300
- * @param req Request object
301
- * @param options object containing flags to enable functionality
302
- * @hidden
303
- */
304
- export function parseRequest ( event : Event , req : ExpressRequest , options ?: ParseRequestOptions ) : Event {
305
- // eslint-disable-next-line no-param-reassign
306
- options = {
307
- ip : false ,
308
- request : true ,
309
- transaction : true ,
310
- user : true ,
311
- ...options ,
312
- } ;
313
-
314
- if ( options . request ) {
315
- // if the option value is `true`, use the default set of keys by not passing anything to `extractRequestData()`
316
- const extractedRequestData = Array . isArray ( options . request )
317
- ? extractRequestData ( req , options . request )
318
- : extractRequestData ( req ) ;
319
- event . request = {
320
- ...event . request ,
321
- ...extractedRequestData ,
322
- } ;
323
- }
324
-
325
- if ( options . user ) {
326
- const extractedUser = req . user && isPlainObject ( req . user ) ? extractUserData ( req . user , options . user ) : { } ;
327
-
328
- if ( Object . keys ( extractedUser ) ) {
329
- event . user = {
330
- ...event . user ,
331
- ...extractedUser ,
332
- } ;
333
- }
334
- }
335
-
336
- // client ip:
337
- // node, nextjs: req.connection.remoteAddress
338
- // express, koa: req.ip
339
- if ( options . ip ) {
340
- const ip = req . ip || ( req . connection && req . connection . remoteAddress ) ;
341
- if ( ip ) {
342
- event . user = {
343
- ...event . user ,
344
- ip_address : ip ,
345
- } ;
346
- }
347
- }
348
-
349
- if ( options . transaction && ! event . transaction ) {
350
- // TODO do we even need this anymore?
351
- // TODO make this work for nextjs
352
- event . transaction = extractTransaction ( req , options . transaction ) ;
353
- }
354
-
355
- return event ;
356
- }
357
-
358
- export type RequestHandlerOptions = ParseRequestOptions & {
73
+ export type RequestHandlerOptions = AddRequestDataToEventOptions & {
359
74
flushTimeout ?: number ;
360
75
} ;
361
76
@@ -407,7 +122,7 @@ export function requestHandler(
407
122
const currentHub = getCurrentHub ( ) ;
408
123
409
124
currentHub . configureScope ( scope => {
410
- scope . addEventProcessor ( ( event : Event ) => parseRequest ( event , req , options ) ) ;
125
+ scope . addEventProcessor ( ( event : Event ) => addRequestDataToEvent ( event , req , options ) ) ;
411
126
const client = currentHub . getClient < NodeClient > ( ) ;
412
127
if ( isAutoSessionTrackingEnabled ( client ) ) {
413
128
const scope = currentHub . getScope ( ) ;
0 commit comments