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 "#/common/utils/envConfig"; 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();