import { captureException , getCurrentHub , startTransaction , withScope } from '@sentry/core' ; import { extractTraceparentData , Span } from '@sentry/tracing' ; import { Event , ExtractedNodeRequestData , Transaction } from '@sentry/types' ; import { isPlainObject , isString , logger , normalize , stripUrlQueryAndFragment } from '@sentry/utils' ; import * as cookie from 'cookie' ; import * as domain from 'domain' ; import * as http from 'http' ; import * as os from 'os' ; import * as url from 'url' ; import { NodeClient } from './client' ; import { flush , isAutoSessionTrackingEnabled } from './sdk' ; export interface ExpressRequest { baseUrl ? : string ; connection ? : { remoteAddress ? : string ; } ; ip ? : string ; method ? : string ; originalUrl ? : string ; route ? : { path : string ; stack : [ { name : string ; } ] ; } ; query ? : { [ key ] : unknown ; } ; url ? : string ; user ? : { [ key ] : any ; } ; } see Exposed export function tracingHandler ( ) { return function sentryTracingMiddleware ( req , res , next ) : void { let traceparentData ; if ( req . headers && isString ( req . headers [ 'sentry-trace' ] ) ) { traceparentData = extractTraceparentData ( req . headers [ 'sentry-trace' ] as string ) ; } const transaction = startTransaction ( { name : extractExpressTransactionName ( req , { path : true , method : true } ) , op : 'http.server' , ... traceparentData , } , { request : extractRequestData ( req ) } ) ; getCurrentHub ( ) . configureScope ( scope => { scope . setSpan ( transaction ) ; } ) ; ( res as any ) . __sentry_transaction = transaction ; res . once ( 'finish' , ( ) => { setImmediate ( ( ) => { addExpressReqToTransaction ( transaction , req ) ; transaction . setHttpStatus ( res . statusCode ) ; transaction . finish ( ) ; } ) ; } ) ; next ( ) ; } ; } /**
 * Set parameterized as transaction name e.g.: `GET /users/:id`
 * Also adds more context data on the transaction from the request
 */ function addExpressReqToTransaction ( transaction , req ) { if ( ! transaction ) return ; transaction . name = extractExpressTransactionName ( req , { path : true , method : true } ) ; transaction . setData ( 'url' , req . originalUrl ) ; transaction . setData ( 'baseUrl' , req . baseUrl ) ; transaction . setData ( 'query' , req . query ) ; } param req param options returns function extractExpressTransactionName ( req , options = { } ) { const method = req . method ?. toUpperCase ( ) ; let path = '' ; if ( req . route ) { path = `${ req . baseUrl || '' }${ req . route . path }` ; } else if ( req . originalUrl || req . url ) { path = stripUrlQueryAndFragment ( req . originalUrl || req . url || '' ) ; } let info = '' ; if ( options . method && method ) { info += method ; } if ( options . method && options . path ) { info += ` ` ; } if ( options . path && path ) { info += path ; } return info ; } type TransactionNamingScheme = 'path' | 'methodPath' | 'handler' ; /** JSDoc */ function extractTransaction ( req , type ) { switch ( type ) { case 'path' : { return extractExpressTransactionName ( req , { path : true } ) ; } case 'handler' : { return req . route ?. stack [ 0 ] . name || '<anonymous>' ; } case 'methodPath' : default : { return extractExpressTransactionName ( req , { path : true , method : true } ) ; } } } /** Default user keys that'll be used to extract data from the request */ const DEFAULT_USER_KEYS = [ 'id' , 'username' , 'email' ] ; /** JSDoc */ function extractUserData ( user , keys ) { const extractedUser = { } ; const attributes = Array . isArray ( keys ) ? keys : DEFAULT_USER_KEYS ; attributes . forEach ( key => { if ( user && key in user ) { extractedUser [ key ] = user [ key ] ; } } ) ; return extractedUser ; } /** Default request keys that'll be used to extract data from the request */ const DEFAULT_REQUEST_KEYS = [ 'cookies' , 'data' , 'headers' , 'method' , 'query_string' , 'url' ] ; param req param keys returns export function extractRequestData ( req , keys = DEFAULT_REQUEST_KEYS ) { const requestData = { } ; const headers = ( req . headers || req . header || { } ) as { host ? : string ; cookie ? : string ; } ; const method = req . method ; const host = req . hostname || req . host || headers . host || '<no host>' ; const protocol = req . protocol === 'https' || req . secure || ( ( req . socket || { } ) as { encrypted ? : boolean ; } ) . encrypted ? 'https' : 'http' ; const originalUrl = ( req . originalUrl || req . url || '' ) as string ; const absoluteUrl = `${ protocol }://${ host }${ originalUrl }` ; keys . forEach ( key => { switch ( key ) { case 'headers' : requestData . headers = headers ; break ; case 'method' : requestData . method = method ; break ; case 'url' : requestData . url = absoluteUrl ; break ; case 'cookies' : requestData . cookies = req . cookies || cookie . parse ( headers . cookie || '' ) ; break ; case 'query_string' : requestData . query_string = req . query || url . parse ( originalUrl || '' , false ) . query ; break ; case 'data' : if ( method === 'GET' || method === 'HEAD' ) { break ; } if ( req . body !== undefined ) { requestData . data = isString ( req . body ) ? req . body : JSON . stringify ( normalize ( req . body ) ) ; } break ; default : if ( { } . hasOwnProperty . call ( req , key ) ) { requestData [ key ] = ( req as { [ key ] : any ; } ) [ key ] ; } } } ) ; return requestData ; } /**
 * Options deciding what parts of the request to use when enhancing an event
 */ export interface ParseRequestOptions { ip ? : boolean ; request ? : boolean | string [ ] ; serverName ? : boolean ; transaction ? : boolean | TransactionNamingScheme ; user ? : boolean | string [ ] ; version ? : boolean ; } param event param req param options hidden export function parseRequest ( event , req , options ? ) { options = { ip : false , request : true , serverName : true , transaction : true , user : true , version : true , ... options , } ; if ( options . version ) { event . contexts = { ... event . contexts , runtime : { name : 'node' , version : global . process . version , } , } ; } if ( options . request ) { const extractedRequestData = Array . isArray ( options . request ) ? extractRequestData ( req , options . request ) : extractRequestData ( req ) ; event . request = { ... event . request , ... extractedRequestData , } ; } if ( options . serverName && ! event . server_name ) { event . server_name = global . process . env . SENTRY_NAME || os . hostname ( ) ; } if ( options . user ) { const extractedUser = req . user && isPlainObject ( req . user ) ? extractUserData ( req . user , options . user ) : { } ; if ( Object . keys ( extractedUser ) ) { event . user = { ... event . user , ... extractedUser , } ; } } if ( options . ip ) { const ip = req . ip || ( req . connection && req . connection . remoteAddress ) ; if ( ip ) { event . user = { ... event . user , ip_address : ip , } ; } } if ( options . transaction && ! event . transaction ) { event . transaction = extractTransaction ( req , options . transaction ) ; } return event ; } export type RequestHandlerOptions = ParseRequestOptions & { flushTimeout ? : number ; } ; see Exposed export function requestHandler ( options ? ) { const currentHub = getCurrentHub ( ) ; const client = currentHub . getClient < NodeClient > ( ) ; if ( client && isAutoSessionTrackingEnabled ( client ) ) { client . initSessionFlusher ( ) ; const scope = currentHub . getScope ( ) ; if ( scope && scope . getSession ( ) ) { scope . setSession ( ) ; } } return function sentryRequestMiddleware ( req , res , next ) : void { if ( options && options . flushTimeout && options . flushTimeout > 0 ) { const _end = res . end ; res . end = function ( chunk ? , encoding ? , cb ? ) : void { void flush ( options . flushTimeout ) . then ( ( ) => { _end . call ( this , chunk , encoding , cb ) ; } ) . then ( null , e => { logger . error ( e ) ; } ) ; } ; } const local = domain . create ( ) ; local . add ( req ) ; local . add ( res ) ; local . on ( 'error' , next ) ; local . run ( ( ) => { const currentHub = getCurrentHub ( ) ; currentHub . configureScope ( scope => { scope . addEventProcessor ( ( event ) => parseRequest ( event , req , options ) ) ; const client = currentHub . getClient < NodeClient > ( ) ; if ( isAutoSessionTrackingEnabled ( client ) ) { const scope = currentHub . getScope ( ) ; if ( scope ) { scope . setRequestSession ( { status : 'ok' } ) ; } } } ) ; res . once ( 'finish' , ( ) => { const client = currentHub . getClient < NodeClient > ( ) ; if ( isAutoSessionTrackingEnabled ( client ) ) { setImmediate ( ( ) => { if ( client && ( client as any ) . _captureRequestSession ) { ( client as any ) . _captureRequestSession ( ) ; } } ) ; } } ) ; next ( ) ; } ) ; } ; } /** JSDoc */ interface MiddlewareError extends Error { status ? : number | string ; statusCode ? : number | string ; status_code ? : number | string ; output ? : { statusCode ? : number | string ; } ; } /** JSDoc */ function getStatusCodeFromResponse ( error ) { const statusCode = error . status || error . statusCode || error . status_code || ( error . output && error . output . statusCode ) ; return statusCode ? parseInt ( statusCode as string , 10 ) : 500 ; } /** Returns true if response code is internal server error */ function defaultShouldHandleError ( error ) { const status = getStatusCodeFromResponse ( error ) ; return status >= 500 ; } see Exposed export function errorHandler ( options ? ) { return function sentryErrorMiddleware ( error , _req , res , next ) : void { const shouldHandleError = ( options && options . shouldHandleError ) || defaultShouldHandleError ; if ( shouldHandleError ( error ) ) { withScope ( _scope => { const transaction = ( res as any ) . __sentry_transaction as Span ; if ( transaction && _scope . getSpan ( ) === undefined ) { _scope . setSpan ( transaction ) ; } const client = getCurrentHub ( ) . getClient < NodeClient > ( ) ; if ( client && isAutoSessionTrackingEnabled ( client ) ) { const isSessionAggregatesMode = ( client as any ) . _sessionFlusher !== undefined ; if ( isSessionAggregatesMode ) { const requestSession = _scope . getRequestSession ( ) ; if ( requestSession && requestSession . status !== undefined ) { requestSession . status = 'crashed' ; } } } const eventId = captureException ( error ) ; ( res as any ) . sentry = eventId ; next ( error ) ; } ) ; return ; } next ( error ) ; } ; }