1+ /* eslint-disable max-lines */
12import type {
23 Event ,
34 ExtractedNodeRequestData ,
45 PolymorphicRequest ,
6+ Request ,
57 TransactionSource ,
68 WebFetchHeaders ,
79 WebFetchRequest ,
@@ -12,6 +14,7 @@ import { DEBUG_BUILD } from './debug-build';
1214import { isPlainObject , isString } from './is' ;
1315import { logger } from './logger' ;
1416import { normalize } from './normalize' ;
17+ import { truncate } from './string' ;
1518import { stripUrlQueryAndFragment } from './url' ;
1619import { getClientIPAddress , ipHeaderNames } from './vendor/getIpAddress' ;
1720
@@ -228,14 +231,27 @@ export function extractRequestData(
228231 if ( method === 'GET' || method === 'HEAD' ) {
229232 break ;
230233 }
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
231239 // body data:
232240 // express, koa, nextjs: req.body
233241 //
234242 // when using node by itself, you have to read the incoming stream(see
235243 // https://nodejs.dev/learn/get-http-request-body-data-using-nodejs); if a user is doing that, we can't know
236244 // 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+ }
239255 }
240256 break ;
241257 }
@@ -250,6 +266,62 @@ export function extractRequestData(
250266 return requestData ;
251267}
252268
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+
253325/**
254326 * Add data from the given request to the given event
255327 *
@@ -374,3 +446,51 @@ export function winterCGRequestToRequestData(req: WebFetchRequest): PolymorphicR
374446 headers,
375447 } ;
376448}
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