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