not sure why those errors got removed we'll see

This commit is contained in:
2025-12-24 16:16:42 -08:00
parent 7399a27600
commit de3f21a7ec
20 changed files with 96 additions and 258 deletions

View File

@@ -1,94 +1,53 @@
// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import {
DatabaseError,
UniqueConstraintError,
ForeignKeyConstraintError,
NotFoundError,
ValidationError,
ValidationIssue,
} from '../services/db/errors.db';
import crypto from 'crypto';
import { ZodError } from 'zod';
import { NotFoundError, UniqueConstraintError, ValidationError } from '../services/db/errors.db';
import { logger } from '../services/logger.server';
interface HttpError extends Error {
status?: number;
}
export const errorHandler = (err: HttpError, req: Request, res: Response, next: NextFunction) => {
// If the response headers have already been sent, we must delegate to the default Express error handler.
/**
* A centralized error handling middleware for the Express application.
* This middleware should be the LAST `app.use()` call to catch all errors from previous routes and middleware.
*
* It standardizes error responses and ensures consistent logging.
*/
export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
// If headers have already been sent, delegate to the default Express error handler.
if (res.headersSent) {
return next(err);
}
// The pino-http middleware guarantees that `req.log` will be available.
const log = req.log;
// Use the request-scoped logger if available, otherwise fall back to the global logger.
const log = req.log || logger;
// --- 1. Determine Final Status Code and Message ---
let statusCode = err.status ?? 500;
const message = err.message;
let validationIssues: ValidationIssue[] | undefined;
let errorId: string | undefined;
// Refine the status code for known error types. Check for most specific types first.
if (err instanceof UniqueConstraintError) {
statusCode = 409; // Conflict
} else if (err instanceof NotFoundError) {
statusCode = 404;
} else if (err instanceof ForeignKeyConstraintError) {
statusCode = 400;
} else if (err instanceof ValidationError) {
statusCode = 400;
validationIssues = err.validationErrors;
} else if (err instanceof DatabaseError) {
// This is a generic fallback for other database errors that are not the specific subclasses above.
statusCode = err.status;
} else if (err.name === 'UnauthorizedError') {
statusCode = err.status || 401;
// --- Handle Zod Validation Errors ---
if (err instanceof ZodError) {
log.warn({ err: err.flatten() }, 'Request validation failed');
return res.status(400).json({
message: 'The request data is invalid.',
errors: err.errors.map((e) => ({ path: e.path, message: e.message })),
});
}
// --- 2. Log Based on Final Status Code ---
// Log the full error details for debugging, especially for server errors.
if (statusCode >= 500) {
errorId = crypto.randomBytes(4).toString('hex');
// The request-scoped logger already contains user, IP, and request_id.
// We add the full error and the request object itself.
// Pino's `redact` config will automatically sanitize sensitive fields in `req`.
log.error(
{
err,
errorId,
req: { method: req.method, url: req.originalUrl, headers: req.headers, body: req.body },
},
`Unhandled API Error (ID: ${errorId})`,
);
} else {
// For 4xx errors, log at a lower level (e.g., 'warn') to avoid flooding error trackers.
// We include the validation errors in the log context if they exist.
log.warn(
{
err,
validationErrors: validationIssues, // Add validation issues to the log object
statusCode,
},
`Client Error on ${req.method} ${req.path}: ${message}`,
);
// --- Handle Custom Operational Errors ---
if (err instanceof NotFoundError) {
log.info({ err }, 'Resource not found');
return res.status(404).json({ message: err.message });
}
// --- TEST ENVIRONMENT DEBUGGING ---
if (process.env.NODE_ENV === 'test') {
console.error('--- [TEST] UNHANDLED ERROR ---', err);
if (err instanceof UniqueConstraintError || err instanceof ValidationError) {
log.warn({ err }, 'Constraint or validation error occurred');
return res.status(400).json({ message: err.message });
}
// --- 3. Send Response ---
// In production, send a generic message for 5xx errors.
// In dev/test, send the actual error message for easier debugging.
const responseMessage =
statusCode >= 500 && process.env.NODE_ENV === 'production'
? `An unexpected server error occurred. Please reference error ID: ${errorId}`
: message;
// --- Handle Generic Errors ---
// Log the full error object for debugging. The pino logger will handle redaction.
log.error({ err }, 'An unhandled error occurred in an Express route');
res.status(statusCode).json({
message: responseMessage,
...(validationIssues && { errors: validationIssues }), // Conditionally add the 'errors' array if it exists
});
};
// In production, send a generic message to avoid leaking implementation details.
if (process.env.NODE_ENV === 'production') {
return res.status(500).json({ message: 'An internal server error occurred.' });
}
// In development, send more details for easier debugging.
return res.status(500).json({ message: err.message, stack: err.stack });
};