1- import type { RequestHandler , Request , Response , ErrorRequestHandler } from 'express' ;
2- import { getClientIp } from 'request-ip' ;
3- import type { Histogram } from '@opentelemetry/api' ;
4- import cleanStack from 'clean-stack' ;
1+ import type {
2+ RequestHandler ,
3+ Request ,
4+ Response ,
5+ ErrorRequestHandler ,
6+ } from "express" ;
7+ import { getClientIp } from "request-ip" ;
8+ import type { Histogram } from "@opentelemetry/api" ;
9+ import cleanStack from "clean-stack" ;
510
6- import { ServiceError } from '../error.js' ;
7- import type { AnyServiceLocals , RequestWithApp , ServiceExpress , ServiceLocals } from '../types.js' ;
8- import type { ServiceHandler } from '../express-app/types.js' ;
9- import type { ConfigurationSchema } from '../config/schema.js' ;
10- import { getNodeEnv } from '../env.js' ;
11+ import { ServiceError } from "../error.js" ;
12+ import type {
13+ AnyServiceLocals ,
14+ RequestWithApp ,
15+ ServiceExpress ,
16+ ServiceLocals ,
17+ } from "../types.js" ;
18+ import type { ServiceHandler } from "../express-app/types.js" ;
19+ import type { ConfigurationSchema } from "../config/schema.js" ;
20+ import { getNodeEnv } from "../env.js" ;
1121
12- const LOG_PREFS = Symbol ( 'Logging information' ) ;
13- const LOGGED_SEMAPHORE = Symbol ( 'Logged semaphore' ) ;
22+ const LOG_PREFS = Symbol ( "Logging information" ) ;
23+ const LOGGED_SEMAPHORE = Symbol ( "Logged semaphore" ) ;
24+ const SUCCESS_SEMAPHORE = Symbol ( "Response finished semaphore" ) ;
1425
1526interface LogPrefs {
1627 start : [ number , number ] ;
@@ -38,12 +49,12 @@ function getBasicInfo(req: Request): [string, Record<string, string | number>] {
3849 const url = req . originalUrl || req . url ;
3950
4051 const preInfo : Record < string , string > = {
41- ip : getClientIp ( req ) || '' ,
52+ ip : getClientIp ( req ) || "" ,
4253 m : req . method ,
4354 } ;
4455
45- if ( req . headers [ ' user-agent' ] ) {
46- preInfo . ua = req . headers [ ' user-agent' ] ;
56+ if ( req . headers [ " user-agent" ] ) {
57+ preInfo . ua = req . headers [ " user-agent" ] ;
4758 }
4859
4960 const sessionReq = req as WithIdentifiedSession ;
@@ -54,7 +65,9 @@ function getBasicInfo(req: Request): [string, Record<string, string | number>] {
5465 return [ url , preInfo ] ;
5566}
5667
57- function finishLog < SLocals extends AnyServiceLocals = ServiceLocals < ConfigurationSchema > > (
68+ function finishLog <
69+ SLocals extends AnyServiceLocals = ServiceLocals < ConfigurationSchema > ,
70+ > (
5871 app : ServiceExpress < SLocals > ,
5972 error : Error | undefined ,
6073 req : Request ,
@@ -80,20 +93,22 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
8093 }
8194 const [ url , preInfo ] = getBasicInfo ( req ) ;
8295
83- let responseType = 'finished' ;
96+ let responseType = "unknown" ;
8497
8598 // ts warning is known and incorrect—`aborted` is a subset of `destroyed`
86- if ( req . aborted ) {
87- responseType = 'aborted' ;
99+ if ( res [ SUCCESS_SEMAPHORE ] ) {
100+ responseType = "finished" ;
101+ } else if ( req . aborted ) {
102+ responseType = "aborted" ;
88103 } else if ( req . destroyed ) {
89- responseType = ' destroyed' ;
104+ responseType = " destroyed" ;
90105 } else if ( error ) {
91- responseType = ' errored' ;
106+ responseType = " errored" ;
92107 }
93108
94109 const endLog : Record < string , string | string [ ] | number | undefined > = {
95110 ...preInfo ,
96- t : ' req' ,
111+ t : " req" ,
97112 r : responseType ,
98113 s : ( error as ErrorWithStatus ) ?. status || res . statusCode || 0 ,
99114 dur,
@@ -125,21 +140,22 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
125140 if ( prefs . logRequests ) {
126141 endLog . h = JSON . stringify ( req . headers ) ;
127142 if ( Buffer . isBuffer ( req . body ) ) {
128- endLog . b = req . body . toString ( ' base64' ) ;
129- } else if ( typeof req . body !== ' string' ) {
143+ endLog . b = req . body . toString ( " base64" ) ;
144+ } else if ( typeof req . body !== " string" ) {
130145 endLog . b = JSON . stringify ( req . body ) ;
131146 } else if ( req . body ) {
132147 endLog . b = req . body ;
133148 }
134149 }
135150
136151 if ( prefs . chunks ?. length ) {
137- const bodyString = Buffer . concat ( prefs . chunks ) . toString ( ' utf8' ) ;
152+ const bodyString = Buffer . concat ( prefs . chunks ) . toString ( " utf8" ) ;
138153 if ( bodyString ) {
139154 endLog . resBody = bodyString ;
140155 }
141156 }
142- const msg = service . getLogFields ?.( req as RequestWithApp < SLocals > , endLog ) || url ;
157+ const msg =
158+ service . getLogFields ?.( req as RequestWithApp < SLocals > , endLog ) || url ;
143159 if ( unexpectedError ) {
144160 logger . error ( endLog , msg ) ;
145161 } else {
@@ -154,14 +170,17 @@ export function loggerMiddleware<
154170> (
155171 app : ServiceExpress < SLocals > ,
156172 histogram : Histogram ,
157- config ?: ConfigurationSchema [ ' logging' ] ,
173+ config ?: ConfigurationSchema [ " logging" ] ,
158174) : RequestHandler {
159- const nonProd = getNodeEnv ( ) !== ' production' ;
175+ const nonProd = getNodeEnv ( ) !== " production" ;
160176 const { logger, service } = app . locals ;
161177 return function serviceLogger ( req , res , next ) {
162178 const logResponse =
163- config ?. logResponseBody || ( nonProd && req . headers [ 'x-log' ] ?. includes ( 'res' ) ) ;
164- const logRequest = config ?. logRequestBody || ( nonProd && req . headers [ 'x-log' ] ?. includes ( 'req' ) ) ;
179+ config ?. logResponseBody ||
180+ ( nonProd && req . headers [ "x-log" ] ?. includes ( "res" ) ) ;
181+ const logRequest =
182+ config ?. logRequestBody ||
183+ ( nonProd && req . headers [ "x-log" ] ?. includes ( "req" ) ) ;
165184 const prefs : LogPrefs = {
166185 start : process . hrtime ( ) ,
167186 logRequests : logRequest ,
@@ -176,44 +195,57 @@ export function loggerMiddleware<
176195 // data is to monkey-patch.
177196 const oldWrite = res . write ;
178197 const oldEnd = res . end ;
179- res . write = ( ( ...args : Parameters < ( typeof res ) [ ' write' ] > ) => {
198+ res . write = ( ( ...args : Parameters < ( typeof res ) [ " write" ] > ) => {
180199 if ( prefs . chunks ) {
181- prefs . chunks . push ( Buffer . isBuffer ( args [ 0 ] ) ? args [ 0 ] : Buffer . from ( args [ 0 ] as string ) ) ;
200+ prefs . chunks . push (
201+ Buffer . isBuffer ( args [ 0 ] ) ? args [ 0 ] : Buffer . from ( args [ 0 ] as string ) ,
202+ ) ;
182203 }
183- return ( oldWrite as ( typeof res ) [ ' write' ] ) . apply ( res , args ) ;
184- } ) as ( typeof res ) [ ' write' ] ;
185- res . end = ( ( ...args : Parameters < ( typeof res ) [ ' end' ] > ) => {
204+ return ( oldWrite as ( typeof res ) [ " write" ] ) . apply ( res , args ) ;
205+ } ) as ( typeof res ) [ " write" ] ;
206+ res . end = ( ( ...args : Parameters < ( typeof res ) [ " end" ] > ) => {
186207 if ( args [ 0 ] && prefs . chunks ) {
187- prefs . chunks . push ( Buffer . isBuffer ( args [ 0 ] ) ? args [ 0 ] : Buffer . from ( args [ 0 ] as string ) ) ;
208+ prefs . chunks . push (
209+ Buffer . isBuffer ( args [ 0 ] ) ? args [ 0 ] : Buffer . from ( args [ 0 ] as string ) ,
210+ ) ;
188211 }
189212 return oldEnd . apply ( res , args ) ;
190- } ) as ( typeof res ) [ ' end' ] ;
213+ } ) as ( typeof res ) [ " end" ] ;
191214 }
192215
193216 if ( config ?. preLog ) {
194217 const [ url , preInfo ] = getBasicInfo ( req ) ;
195218 const preLog : Record < string , string | string [ ] | number | undefined > = {
196219 ...preInfo ,
197- t : ' pre' ,
220+ t : " pre" ,
198221 ref : req . headers . referer || undefined ,
199222 sid : ( req as WithIdentifiedSession ) . session ?. id ,
200223 c : req . headers . correlationid || undefined ,
201224 } ;
202- const msg = service . getLogFields ?.( req as RequestWithApp < SLocals > , preLog ) || url ;
225+ const msg =
226+ service . getLogFields ?.( req as RequestWithApp < SLocals > , preLog ) || url ;
203227 logger . info ( preLog , msg ) ;
204228 }
205229
206230 const logWriter = ( err ?: Error ) => finishLog ( app , err , req , res , histogram ) ;
207- res . on ( 'finish' , logWriter ) ;
208- res . on ( 'close' , logWriter ) ;
209- res . on ( 'error' , logWriter ) ;
231+ res . on ( "finish" , ( err ?: Error ) => {
232+ res [ SUCCESS_SEMAPHORE ] = true ;
233+ logWriter ( err ) ;
234+ } ) ;
235+ res . on ( "close" , logWriter ) ;
236+ res . on ( "error" , logWriter ) ;
210237 next ( ) ;
211238 } ;
212239}
213240
214241export function errorHandlerMiddleware <
215242 SLocals extends AnyServiceLocals = ServiceLocals < ConfigurationSchema > ,
216- > ( app : ServiceExpress < SLocals > , histogram : Histogram , unnest ?: boolean , returnError ?: boolean ) {
243+ > (
244+ app : ServiceExpress < SLocals > ,
245+ histogram : Histogram ,
246+ unnest ?: boolean ,
247+ returnError ?: boolean ,
248+ ) {
217249 const svcErrorHandler : ErrorRequestHandler = ( error , req , res , next ) => {
218250 let loggable : Partial < ServiceError > = error ;
219251 const body = error . response ?. body || error . body ;
@@ -249,11 +281,15 @@ export function errorHandlerMiddleware<
249281
250282export function notFoundMiddleware ( ) {
251283 const serviceNotFoundHandler : ServiceHandler = ( req , res , next ) => {
252- const error = new ServiceError ( req . app , `Cannot ${ req . method } ${ req . path } ` , {
253- status : 404 ,
254- code : 'NotFound' ,
255- domain : 'http' ,
256- } ) ;
284+ const error = new ServiceError (
285+ req . app ,
286+ `Cannot ${ req . method } ${ req . path } ` ,
287+ {
288+ status : 404 ,
289+ code : "NotFound" ,
290+ domain : "http" ,
291+ } ,
292+ ) ;
257293 next ( error ) ;
258294 } ;
259295 return serviceNotFoundHandler as RequestHandler ;
0 commit comments