import { createLogger, format, transports } from "winston"; import dotenv from "dotenv"; dotenv.config(); class Logger { constructor() { this.logCache = []; this.maxCacheSize = 1000; const consoleFormat = format.printf( ({ level, message, service, method, details, timestamp, stack }) => { if (message instanceof Object) { message = JSON.stringify(message, null, 2); } if (details instanceof Object) { details = JSON.stringify(details, null, 2); } let msg = `${timestamp} ${level}:`; service && (msg += ` [${service}]`); method && (msg += `(${method})`); message && (msg += ` ${message}`); details && (msg += ` (details: ${details})`); if (typeof stack !== "undefined") { const stackTrace = stack ?.split("\n") .slice(1) // Remove first line (error message) .map((line) => { const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/); if (match) { return { function: match[1], file: match[2], line: parseInt(match[3]), column: parseInt(match[4]), }; } return line.trim(); }); stack && (msg += ` (stack: ${JSON.stringify(stackTrace, null, 2)})`); } return msg; } ); const logLevel = process.env.LOG_LEVEL || "info"; this.logger = createLogger({ level: logLevel, format: format.combine(format.timestamp()), transports: [ new transports.Console({ format: format.combine( format.colorize(), format.prettyPrint(), format.json(), consoleFormat ), }), new transports.File({ format: format.combine(format.json()), filename: "app.log", }), ], }); } /** * Logs an informational message. * @param {Object} config - The configuration object. * @param {string} config.message - The message to log. * @param {string} config.service - The service name. * @param {string} config.method - The method name. * @param {Object} config.details - Additional details. */ info(config) { const logEntry = this.buildLogEntry("info", config); this.cacheLog(logEntry); this.logger.info(logEntry); } /** * Logs a warning message. * @param {Object} config - The configuration object. * @param {string} config.message - The message to log. * @param {string} config.service - The service name. * @param {string} config.method - The method name. * @param {Object} config.details - Additional details. */ warn(config) { const logEntry = this.buildLogEntry("warn", config); this.cacheLog(logEntry); this.logger.warn(logEntry); } /** * Logs an error message. * @param {Object} config - The configuration object. * @param {string} config.message - The message to log. * @param {string} config.service - The service name. * @param {string} config.method - The method name. * @param {Object} config.details - Additional details. */ error(config) { const logEntry = this.buildLogEntry("error", config); this.cacheLog(logEntry); this.logger.error(logEntry); } /** * Logs a debug message. * @param {Object} config - The configuration object. * @param {string} config.message - The message to log. * @param {string} config.service - The service name. * @param {string} config.method - The method name. * @param {Object} config.details - Additional details. */ debug(config) { const logEntry = this.buildLogEntry("debug", config); this.cacheLog(logEntry); this.logger.debug(logEntry); } cacheLog(entry) { this.logCache.push(entry); if (this.logCache.length > this.maxCacheSize) { this.logCache.shift(); } } getLogs() { return this.logCache; } buildLogEntry(level, config) { return { level, message: config.message, service: config.service, method: config.method, details: config.details, stack: config.stack, timestamp: new Date().toISOString(), }; } } const logger = new Logger(); export { Logger }; export default logger;