import { createLogger, format, transports, Logger, LeveledLogMethod } from 'winston'; import * as fs from 'fs'; import * as path from 'path'; // Define log directory const LOG_DIR = path.join(process.cwd(), 'logs'); // Ensure log directory exists if (!fs.existsSync(LOG_DIR)) { fs.mkdirSync(LOG_DIR, { recursive: true }); } // Interface for topic logger options interface TopicLoggerOptions { topic: string; level?: string; } // Create the base logger const createBaseLogger = (level: string = 'debug') => { return createLogger({ level, format: format.combine( format.timestamp(), format.json() ), transports: [ new transports.Console({ format: format.combine( format.colorize(), format.printf(({ timestamp, level, message, topic, ...meta }) => { const topicStr = topic ? `[${topic}] ` : ''; return `${timestamp} ${level}: ${topicStr}${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ''}`; }) ) }), new transports.File({ filename: path.join(LOG_DIR, 'combined.log') }) ] }); }; // Main logger instance const logger = createBaseLogger(); // Create a topic-specific logger const createTopicLogger = (options: TopicLoggerOptions): Logger => { const topicLogger = createBaseLogger(options.level); // Create a wrapper that adds topic to all log messages const wrappedLogger = { ...topicLogger, }; // Wrap each log level method to include topic (['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'] as const).forEach((level) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any wrappedLogger[level] = ((...args: any[]) => { // Handle different call patterns if (typeof args[0] === 'string') { const message = args[0]; const meta = args[1] || {}; return topicLogger[level]({ message, topic: options.topic, ...meta }); } else { // If first argument is an object, add topic to it return topicLogger[level]({ ...args[0], topic: options.topic }); } }) as LeveledLogMethod; }); return wrappedLogger as Logger; }; export { logger, createTopicLogger }; export default logger;