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()