Scratch space for learning atproto app development
1import { randomUUID } from 'node:crypto' 2import type { IncomingMessage, ServerResponse } from 'node:http' 3import type { Request, RequestHandler, Response } from 'express' 4import type { LevelWithSilent } from 'pino' 5import { type CustomAttributeKeys, type Options, pinoHttp } from 'pino-http' 6 7import { env } from '#/env' 8 9enum LogLevel { 10 Fatal = 'fatal', 11 Error = 'error', 12 Warn = 'warn', 13 Info = 'info', 14 Debug = 'debug', 15 Trace = 'trace', 16 Silent = 'silent', 17} 18 19type PinoCustomProps = { 20 request: Request 21 response: Response 22 error: Error 23 responseBody: unknown 24} 25 26const requestLogger = (options?: Options): RequestHandler[] => { 27 const pinoOptions: Options = { 28 enabled: env.isProduction, 29 customProps: customProps as unknown as Options['customProps'], 30 redact: [], 31 genReqId, 32 customLogLevel, 33 customSuccessMessage, 34 customReceivedMessage: (req) => `request received: ${req.method}`, 35 customErrorMessage: (_req, res) => 36 `request errored with status code: ${res.statusCode}`, 37 customAttributeKeys, 38 ...options, 39 } 40 return [responseBodyMiddleware, pinoHttp(pinoOptions)] 41} 42 43const customAttributeKeys: CustomAttributeKeys = { 44 req: 'request', 45 res: 'response', 46 err: 'error', 47 responseTime: 'timeTaken', 48} 49 50const customProps = (req: Request, res: Response): PinoCustomProps => ({ 51 request: req, 52 response: res, 53 error: res.locals.err, 54 responseBody: res.locals.responseBody, 55}) 56 57const responseBodyMiddleware: RequestHandler = (_req, res, next) => { 58 const isNotProduction = !env.isProduction 59 if (isNotProduction) { 60 const originalSend = res.send 61 res.send = (content) => { 62 res.locals.responseBody = content 63 res.send = originalSend 64 return originalSend.call(res, content) 65 } 66 } 67 next() 68} 69 70const customLogLevel = ( 71 _req: IncomingMessage, 72 res: ServerResponse<IncomingMessage>, 73 err?: Error 74): LevelWithSilent => { 75 if (err || res.statusCode >= 500) return LogLevel.Error 76 if (res.statusCode >= 400) return LogLevel.Warn 77 if (res.statusCode >= 300) return LogLevel.Silent 78 return LogLevel.Info 79} 80 81const customSuccessMessage = ( 82 req: IncomingMessage, 83 res: ServerResponse<IncomingMessage> 84) => { 85 if (res.statusCode === 404) return 'Not found' 86 return `${req.method} completed` 87} 88 89const genReqId = ( 90 req: IncomingMessage, 91 res: ServerResponse<IncomingMessage> 92) => { 93 const existingID = req.id ?? req.headers['x-request-id'] 94 if (existingID) return existingID 95 const id = randomUUID() 96 res.setHeader('X-Request-Id', id) 97 return id 98} 99 100export default requestLogger()