import { env } from "$env/dynamic/private"; import path from "path"; import util from "util"; import winston from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; import type { Err } from "./result"; 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 = env.LOG_LEVEL?.toLowerCase(); if (envLevel && envLevel in levels) { return envLevel; } return env.NODE_ENV === "development" ? "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: any) => { 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(), ); // Log directory - use logs folder in project root const logDir = path.join(process.cwd(), "logs"); // Daily rotate file transport for all logs const dailyRotateFileTransport = new DailyRotateFile({ filename: path.join(logDir, "app-%DATE%.log"), datePattern: "YYYY-MM-DD", zippedArchive: true, maxSize: "20m", maxFiles: "14d", // Keep logs for 14 days format: fileFormat, }); // Daily rotate file transport for errors only const dailyRotateErrorTransport = new DailyRotateFile({ filename: path.join(logDir, "error-%DATE%.log"), datePattern: "YYYY-MM-DD", zippedArchive: true, maxSize: "20m", maxFiles: "30d", // Keep error logs for 30 days level: "error", format: fileFormat, }); // Build transports const transports: winston.transport[] = [ new winston.transports.Console({ format: consoleFormat }), dailyRotateFileTransport, dailyRotateErrorTransport, ]; winston.addColors(colors); const logger = winston.createLogger({ level: level(), levels, transports, format: fileFormat, // Default format for all transports exceptionHandlers: [dailyRotateFileTransport, dailyRotateErrorTransport], rejectionHandlers: [dailyRotateFileTransport, dailyRotateErrorTransport], }); 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 };