1
+ /* eslint-disable max-lines */
1
2
import type {
2
3
Event ,
3
4
ExtractedNodeRequestData ,
4
5
PolymorphicRequest ,
6
+ Request ,
5
7
TransactionSource ,
6
8
WebFetchHeaders ,
7
9
WebFetchRequest ,
@@ -12,6 +14,7 @@ import { DEBUG_BUILD } from './debug-build';
12
14
import { isPlainObject , isString } from './is' ;
13
15
import { logger } from './logger' ;
14
16
import { normalize } from './normalize' ;
17
+ import { truncate } from './string' ;
15
18
import { stripUrlQueryAndFragment } from './url' ;
16
19
import { getClientIPAddress , ipHeaderNames } from './vendor/getIpAddress' ;
17
20
@@ -228,14 +231,27 @@ export function extractRequestData(
228
231
if ( method === 'GET' || method === 'HEAD' ) {
229
232
break ;
230
233
}
234
+ // NOTE: As of v8, request is (unless a user sets this manually) ALWAYS a http request
235
+ // Which does not have a body by default
236
+ // However, in our http instrumentation, we patch the request to capture the body and store it on the
237
+ // request as `.body` anyhow
238
+ // In v9, we may update requestData to only work with plain http requests
231
239
// body data:
232
240
// express, koa, nextjs: req.body
233
241
//
234
242
// when using node by itself, you have to read the incoming stream(see
235
243
// https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
236
244
// where they're going to store the final result, so they'll have to capture this data themselves
237
- if ( req . body !== undefined ) {
238
- requestData . data = isString ( req . body ) ? req . body : JSON . stringify ( normalize ( req . body ) ) ;
245
+ const body = req . body ;
246
+ if ( body !== undefined ) {
247
+ const stringBody : string = isString ( body )
248
+ ? body
249
+ : isPlainObject ( body )
250
+ ? JSON . stringify ( normalize ( body ) )
251
+ : truncate ( `${ body } ` , 1024 ) ;
252
+ if ( stringBody ) {
253
+ requestData . data = stringBody ;
254
+ }
239
255
}
240
256
break ;
241
257
}
@@ -250,6 +266,62 @@ export function extractRequestData(
250
266
return requestData ;
251
267
}
252
268
269
+ /**
270
+ * Add already normalized request data to an event.
271
+ */
272
+ export function addNormalizedRequestDataToEvent (
273
+ event : Event ,
274
+ req : Request ,
275
+ // This is non-standard data that is not part of the regular HTTP request
276
+ additionalData : { ipAddress ?: string ; user ?: Record < string , unknown > } ,
277
+ options : AddRequestDataToEventOptions ,
278
+ ) : Event {
279
+ const include = {
280
+ ...DEFAULT_INCLUDES ,
281
+ ...( options && options . include ) ,
282
+ } ;
283
+
284
+ if ( include . request ) {
285
+ const includeRequest = Array . isArray ( include . request ) ? [ ...include . request ] : [ ...DEFAULT_REQUEST_INCLUDES ] ;
286
+ if ( include . ip ) {
287
+ includeRequest . push ( 'ip' ) ;
288
+ }
289
+
290
+ const extractedRequestData = extractNormalizedRequestData ( req , { include : includeRequest } ) ;
291
+
292
+ event . request = {
293
+ ...event . request ,
294
+ ...extractedRequestData ,
295
+ } ;
296
+ }
297
+
298
+ if ( include . user ) {
299
+ const extractedUser =
300
+ additionalData . user && isPlainObject ( additionalData . user )
301
+ ? extractUserData ( additionalData . user , include . user )
302
+ : { } ;
303
+
304
+ if ( Object . keys ( extractedUser ) . length ) {
305
+ event . user = {
306
+ ...event . user ,
307
+ ...extractedUser ,
308
+ } ;
309
+ }
310
+ }
311
+
312
+ if ( include . ip ) {
313
+ const ip = ( req . headers && getClientIPAddress ( req . headers ) ) || additionalData . ipAddress ;
314
+ if ( ip ) {
315
+ event . user = {
316
+ ...event . user ,
317
+ ip_address : ip ,
318
+ } ;
319
+ }
320
+ }
321
+
322
+ return event ;
323
+ }
324
+
253
325
/**
254
326
* Add data from the given request to the given event
255
327
*
@@ -374,3 +446,51 @@ export function winterCGRequestToRequestData(req: WebFetchRequest): PolymorphicR
374
446
headers,
375
447
} ;
376
448
}
449
+
450
+ function extractNormalizedRequestData ( normalizedRequest : Request , { include } : { include : string [ ] } ) : Request {
451
+ const includeKeys = include ? ( Array . isArray ( include ) ? include : DEFAULT_REQUEST_INCLUDES ) : [ ] ;
452
+
453
+ const requestData : Request = { } ;
454
+
455
+ const { headers } = normalizedRequest ;
456
+
457
+ if ( includeKeys . includes ( 'headers' ) ) {
458
+ requestData . headers = headers ;
459
+
460
+ // Remove the Cookie header in case cookie data should not be included in the event
461
+ if ( ! include . includes ( 'cookies' ) ) {
462
+ delete ( headers as { cookie ?: string } ) . cookie ;
463
+ }
464
+
465
+ // Remove IP headers in case IP data should not be included in the event
466
+ if ( ! include . includes ( 'ip' ) ) {
467
+ ipHeaderNames . forEach ( ipHeaderName => {
468
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
469
+ delete ( headers as Record < string , unknown > ) [ ipHeaderName ] ;
470
+ } ) ;
471
+ }
472
+ }
473
+
474
+ if ( includeKeys . includes ( 'method' ) ) {
475
+ requestData . method = normalizedRequest . method ;
476
+ }
477
+
478
+ if ( includeKeys . includes ( 'url' ) ) {
479
+ requestData . url = normalizedRequest . url ;
480
+ }
481
+
482
+ if ( includeKeys . includes ( 'cookies' ) ) {
483
+ const cookies = normalizedRequest . cookies || ( headers && headers . cookie ? parseCookie ( headers . cookie ) : undefined ) ;
484
+ requestData . cookies = cookies ;
485
+ }
486
+
487
+ if ( includeKeys . includes ( 'query_string' ) ) {
488
+ requestData . query_string = normalizedRequest . query_string ;
489
+ }
490
+
491
+ if ( includeKeys . includes ( 'data' ) ) {
492
+ requestData . data = normalizedRequest . data ;
493
+ }
494
+
495
+ return requestData ;
496
+ }
0 commit comments