Extract attachErrorContext helper to DRY up the error handler, attach the original Error object to res.err so pino can serialize stack traces, and rename the log context key from err to errorContext so it doesn't clash with pino's built-in err serializer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
1.7 KiB
TypeScript
72 lines
1.7 KiB
TypeScript
import type { Request, Response, NextFunction } from "express";
|
|
import { ZodError } from "zod";
|
|
import { HttpError } from "../errors.js";
|
|
|
|
export interface ErrorContext {
|
|
error: { message: string; stack?: string; name?: string; details?: unknown; raw?: unknown };
|
|
method: string;
|
|
url: string;
|
|
reqBody?: unknown;
|
|
reqParams?: unknown;
|
|
reqQuery?: unknown;
|
|
}
|
|
|
|
function attachErrorContext(
|
|
req: Request,
|
|
res: Response,
|
|
payload: ErrorContext["error"],
|
|
rawError?: Error,
|
|
) {
|
|
(res as any).__errorContext = {
|
|
error: payload,
|
|
method: req.method,
|
|
url: req.originalUrl,
|
|
reqBody: req.body,
|
|
reqParams: req.params,
|
|
reqQuery: req.query,
|
|
} satisfies ErrorContext;
|
|
if (rawError) {
|
|
(res as any).err = rawError;
|
|
}
|
|
}
|
|
|
|
export function errorHandler(
|
|
err: unknown,
|
|
req: Request,
|
|
res: Response,
|
|
_next: NextFunction,
|
|
) {
|
|
if (err instanceof HttpError) {
|
|
if (err.status >= 500) {
|
|
attachErrorContext(
|
|
req,
|
|
res,
|
|
{ message: err.message, stack: err.stack, name: err.name, details: err.details },
|
|
err,
|
|
);
|
|
}
|
|
res.status(err.status).json({
|
|
error: err.message,
|
|
...(err.details ? { details: err.details } : {}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (err instanceof ZodError) {
|
|
res.status(400).json({ error: "Validation error", details: err.errors });
|
|
return;
|
|
}
|
|
|
|
const rootError = err instanceof Error ? err : new Error(String(err));
|
|
attachErrorContext(
|
|
req,
|
|
res,
|
|
err instanceof Error
|
|
? { message: err.message, stack: err.stack, name: err.name }
|
|
: { message: String(err), raw: err, stack: rootError.stack, name: rootError.name },
|
|
rootError,
|
|
);
|
|
|
|
res.status(500).json({ error: "Internal server error" });
|
|
}
|