const winston = require('winston'); const DailyRotateFile = require('winston-daily-rotate-file'); const path = require('path'); // Create logs directory if it doesn't exist const logsDir = path.join(__dirname, '..', 'logs'); // Define log levels and colors const logLevels = { error: 0, warn: 1, info: 2, http: 3, debug: 4 }; const logColors = { error: 'red', warn: 'yellow', info: 'green', http: 'magenta', debug: 'blue' }; winston.addColors(logColors); // Custom format for structured logging const logFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.json(), winston.format.printf(({ timestamp, level, message, stack, ...meta }) => { let logMessage = `${timestamp} [${level.toUpperCase()}]: ${message}`; // Add stack trace for errors if (stack) { logMessage += `\nStack: ${stack}`; } // Add metadata if present if (Object.keys(meta).length > 0) { logMessage += `\nMeta: ${JSON.stringify(meta, null, 2)}`; } return logMessage; }) ); // Console format for development const consoleFormat = winston.format.combine( winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, stack }) => { let logMessage = `${timestamp} ${level}: ${message}`; if (stack) { logMessage += `\n${stack}`; } return logMessage; }) ); // Create transports const transports = [ // Console transport for development new winston.transports.Console({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', format: consoleFormat, handleExceptions: true, handleRejections: true }), // File transport for all logs new DailyRotateFile({ filename: path.join(logsDir, 'application-%DATE%.log'), datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '14d', level: 'debug', format: logFormat, handleExceptions: true, handleRejections: true }), // Separate file for errors new DailyRotateFile({ filename: path.join(logsDir, 'error-%DATE%.log'), datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '30d', level: 'error', format: logFormat, handleExceptions: true, handleRejections: true }), // HTTP requests log new DailyRotateFile({ filename: path.join(logsDir, 'http-%DATE%.log'), datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '7d', level: 'http', format: logFormat }) ]; // Create logger instance const logger = winston.createLogger({ levels: logLevels, transports, exitOnError: false }); // Add request logging helper logger.logRequest = (req, res, responseTime) => { const logData = { method: req.method, url: req.originalUrl, ip: req.ip || req.connection.remoteAddress, userAgent: req.get('User-Agent'), statusCode: res.statusCode, responseTime: `${responseTime}ms`, contentLength: res.get('Content-Length') || 0 }; // Log based on status code if (res.statusCode >= 500) { logger.error('HTTP Request Error', logData); } else if (res.statusCode >= 400) { logger.warn('HTTP Request Warning', logData); } else { logger.http('HTTP Request', logData); } }; // Add database operation logging helper logger.logDbOperation = (operation, table, data = {}, duration = null) => { const logData = { operation, table, duration: duration ? `${duration}ms` : null, ...data }; logger.debug('Database Operation', logData); }; // Add error context helper logger.logError = (error, context = {}) => { const errorData = { message: error.message, stack: error.stack, name: error.name, code: error.code, ...context }; logger.error('Application Error', errorData); }; // Add business logic logging helper logger.logBusinessEvent = (event, data = {}) => { logger.info(`Business Event: ${event}`, data); }; module.exports = logger;