import DailyRotateFile from "winston-daily-rotate-file"; import { settings } from "@pkg/settings"; import type { Err } from "@pkg/result"; import winston from "winston"; import util from "util"; import path from "path"; process.on("warning", (warning) => { const msg = String(warning?.message || ""); const name = String((warning as any)?.name || ""); // Ignore the noisy timer warning from Node/kafkajs interplay if ( name === "TimeoutNegativeWarning" || msg.includes("TimeoutNegativeWarning") || msg.includes("Timeout duration was set to 1") ) { return; } // Keep other warnings visible console.warn(warning); }); const levels = { error: 0, warn: 1, info: 2, http: 3, debug: 4, }; const colors = { error: "red", warn: "yellow", info: "green", http: "magenta", debug: "white", }; const level = () => { const envLevel = process.env.LOG_LEVEL?.toLowerCase(); if (envLevel && envLevel in levels) { return envLevel; } return settings.isDevelopment ? "debug" : "warn"; }; // Console format with colors const consoleFormat = winston.format.combine( winston.format.errors({ stack: true }), winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), winston.format.colorize({ all: true }), winston.format.printf((info) => { const { level, message, timestamp, ...extra } = info; let formattedMessage = ""; if (message instanceof Error) { formattedMessage = message.stack || message.message; } else if (typeof message === "object") { formattedMessage = util.inspect(message, { depth: null, colors: true, }); } else { formattedMessage = message as any as string; } // Handle extra fields (if any) const formattedExtra = Object.keys(extra).length > 0 ? `\n${util.inspect(extra, { depth: null, colors: true })}` : ""; return `[${level}] ${timestamp}: ${formattedMessage}${formattedExtra}`; }), ); // JSON format for file logging const fileFormat = winston.format.combine( winston.format.errors({ stack: true }), winston.format.timestamp(), winston.format.json(), ); // File transport with daily rotation const fileTransport = new DailyRotateFile({ filename: path.join("logs", "app-%DATE%.log"), datePattern: "YYYY-MM-DD", maxSize: "20m", maxFiles: "14d", format: fileFormat, }); // Error file transport with daily rotation const errorFileTransport = new DailyRotateFile({ filename: path.join("logs", "error-%DATE%.log"), datePattern: "YYYY-MM-DD", maxSize: "20m", maxFiles: "14d", level: "error", format: fileFormat, }); const transports: winston.transport[] = [ new winston.transports.Console({ format: consoleFormat }), fileTransport, errorFileTransport, ]; winston.addColors(colors); const logger = winston.createLogger({ level: level(), levels, transports, format: fileFormat, exceptionHandlers: [ new winston.transports.Console({ format: consoleFormat }), fileTransport, ], rejectionHandlers: [ new winston.transports.Console({ format: consoleFormat }), fileTransport, ], }); const stream = { write: (message: string) => logger.http(message.trim()) }; function getError(payload: Err, error?: any) { logger.error(JSON.stringify({ payload, error }, null, 2)); console.error(error); return { code: payload.code, message: payload.message, description: payload.description, detail: payload.detail, error: error instanceof Error ? error.message : error, actionable: payload.actionable, } as Err; } export { getError, logger, stream };