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