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