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 { StatusCodes, getReasonPhrase } from 'http-status-codes' 5import type { LevelWithSilent } from 'pino' 6import { type CustomAttributeKeys, type Options, pinoHttp } from 'pino-http' 7 8import { env } from '#/env' 9 10enum LogLevel { 11 Fatal = 'fatal', 12 Error = 'error', 13 Warn = 'warn', 14 Info = 'info', 15 Debug = 'debug', 16 Trace = 'trace', 17 Silent = 'silent', 18} 19 20type PinoCustomProps = { 21 request: Request 22 response: Response 23 error: Error 24 responseBody: unknown 25} 26 27const requestLogger = (options?: Options): RequestHandler[] => { 28 const pinoOptions: Options = { 29 enabled: env.isProduction, 30 customProps: customProps as unknown as Options['customProps'], 31 redact: [], 32 genReqId, 33 customLogLevel, 34 customSuccessMessage, 35 customReceivedMessage: (req) => `request received: ${req.method}`, 36 customErrorMessage: (_req, res) => `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 = (_req: IncomingMessage, res: ServerResponse<IncomingMessage>, err?: Error): LevelWithSilent => { 71 if (err || res.statusCode >= StatusCodes.INTERNAL_SERVER_ERROR) return LogLevel.Error 72 if (res.statusCode >= StatusCodes.BAD_REQUEST) return LogLevel.Warn 73 if (res.statusCode >= StatusCodes.MULTIPLE_CHOICES) return LogLevel.Silent 74 return LogLevel.Info 75} 76 77const customSuccessMessage = (req: IncomingMessage, res: ServerResponse<IncomingMessage>) => { 78 if (res.statusCode === StatusCodes.NOT_FOUND) return getReasonPhrase(StatusCodes.NOT_FOUND) 79 return `${req.method} completed` 80} 81 82const genReqId = (req: IncomingMessage, res: ServerResponse<IncomingMessage>) => { 83 const existingID = req.id ?? req.headers['x-request-id'] 84 if (existingID) return existingID 85 const id = randomUUID() 86 res.setHeader('X-Request-Id', id) 87 return id 88} 89 90export default requestLogger()