diff --git a/server/src/domain/entities/CustomError.ts b/server/src/domain/entities/CustomError.ts index b2f2d26c..44994af7 100644 --- a/server/src/domain/entities/CustomError.ts +++ b/server/src/domain/entities/CustomError.ts @@ -1,9 +1,21 @@ import { StatusCode } from "../../types"; -export default class CustomError extends Error { - public statusCode: StatusCode - constructor(message: string, statusCode: StatusCode) { +export default class AppError extends Error { + public statusCode: StatusCode; + public code?: string; + public details?: any; + + constructor(message: string, statusCode: StatusCode, code?: string, details?: any) { super(message); - this.statusCode = statusCode + this.statusCode = statusCode; + this.code = code; + this.details = details; + } + public toJSON() { + return { + message: this.message, + statusCode: this.statusCode, + code: this.code + }; } } diff --git a/server/src/presentation/middlewares/ErrorHandler.ts b/server/src/presentation/middlewares/ErrorHandler.ts index 6fe1ab4d..0d311a89 100644 --- a/server/src/presentation/middlewares/ErrorHandler.ts +++ b/server/src/presentation/middlewares/ErrorHandler.ts @@ -4,25 +4,30 @@ import logger from "../../utils/logger"; import CustomError from "../../domain/entities/CustomError"; export default class ErrorHandler { - exec(err: any, req: Request, res: Response, next: NextFunction) { - logger.error(err.message, { - statusCode: err.statusCode || StatusCode.InternalServerError, - stack: err.stack, - path: req.path, - method: req.method, - ip: req.ip, - }); - + exec(error: any, req: Request, res: Response, next: NextFunction) { + const statusCode = error.statusCode || StatusCode.InternalServerError; + const message = error.message || "Internal Server Error"; + const clientIp = req.ip || req.connection.remoteAddress; - const statusCode = err.statusCode || StatusCode.InternalServerError; - const message = err.message || "Internal Server Error"; - - if (err instanceof CustomError) { + if (error instanceof CustomError) { + logger.warn(`CustomError: ${message}`, { + statusCode, + method: req.method, + path: req.path, + clientIp, + }); return res.status(statusCode).json({ message }); } - if (err.code && err.code === 11000) { - logger.warn("Duplicate key error encountered."); + if (error.code && error.code === 11000) { + logger.warn("Resource Conflict (Duplicate Entry)", { + statusCode: StatusCode.Conflict, + method: req.method, + path: req.path, + clientIp, + requestHeaders: req.headers, + error: message, + }); return res.status(StatusCode.Conflict).json({ message: "The resource already exists.", error: message, @@ -33,15 +38,30 @@ export default class ErrorHandler { message.includes("getaddrinfo ENOTFOUND smtp.gmail.com") || message.includes("queryA ETIMEOUT smtp.gmail.com") ) { - logger.error("Email service issue encountered."); + logger.error("Email Service Error", { + statusCode: StatusCode.InternalServerError, + method: req.method, + path: req.path, + clientIp, + requestHeaders: req.headers, + }); return res.status(StatusCode.InternalServerError).json({ - message: "We are Having Issue with Email Service", + message: "We are having an issue with the Email Service.", }); - } + } + + logger.error(`Unhandled Error: ${message}`, { + statusCode, + method: req.method, + path: req.path, + clientIp, + requestHeaders: req.headers, + stack: error.stack, + }); res.status(statusCode).json({ message, - ...(process.env.NODE_ENV !== "production" && { stack: err.stack }), + ...(process.env.NODE_ENV !== "production" && { stack: error.stack }), }); } } diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index cbd00ba6..12e31607 100644 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -4,11 +4,13 @@ import "winston-daily-rotate-file"; const logDirectory = path.resolve(__dirname, "logs"); -const customFormat = format.printf(({ timestamp, level, message, stack, url, data, ...meta }) => { +const consoleFormat = format.printf(({ timestamp, level, message, stack, url, data, ...meta }) => { const logLevel = level.toUpperCase(); const urlInfo = url ? ` | URL: ${url}` : ""; const dataInfo = data ? ` | Data: ${JSON.stringify(data)}` : ""; - return `${timestamp} [${logLevel}]: ${message}${stack ? ` | Stack: ${stack}` : ""}${urlInfo}${dataInfo}${Object.keys(meta).length ? ` | Meta: ${JSON.stringify(meta)}` : ""}`; + const metaInfo = Object.keys(meta).length ? ` | Meta: ${JSON.stringify(meta, null, 2)}` : ""; + + return `${timestamp} [${logLevel}]: ${message}${stack ? `\nStack: ${stack}` : ""}${urlInfo}${dataInfo}${metaInfo}`; }); const logger = createLogger({ @@ -22,7 +24,7 @@ const logger = createLogger({ defaultMeta: { service: "service" }, transports: [ new transports.DailyRotateFile({ - filename: path.join(logDirectory, "error-%DATE%.log"), + filename: path.join(logDirectory, "%DATE%-error.log"), datePattern: "DD-MM-YYYY", level: "error", maxSize: "20m", @@ -30,7 +32,7 @@ const logger = createLogger({ zippedArchive: true, }), new transports.DailyRotateFile({ - filename: path.join(logDirectory, "combined-%DATE%.log"), + filename: path.join(logDirectory, "%DATE%-combined.log"), datePattern: "DD-MM-YYYY", maxSize: "20m", maxFiles: "14d", @@ -42,9 +44,15 @@ const logger = createLogger({ if (process.env.NODE_ENV !== "production") { logger.add( new transports.Console({ - format: format.combine(format.colorize(), customFormat), + level: "debug", + format: format.combine( + format.colorize({ all: true }), + format.timestamp({ format: "DD-MM-YYYY HH:mm:ss" }), + consoleFormat + ), }) ); } + export default logger;