const winston = require('winston'); const DailyRotateFile = require('winston-daily-rotate-file'); const path = require('path'); // Define log levels const levels = { error: 0, warn: 1, info: 2, http: 3, debug: 4, }; // Define colors for each level const colors = { error: 'red', warn: 'yellow', info: 'green', http: 'magenta', debug: 'white', }; // Tell winston about our colors winston.addColors(colors); // Define which level to log based on environment const level = () => { const env = process.env.NODE_ENV || 'development'; const isDevelopment = env === 'development'; return isDevelopment ? 'debug' : 'info'; }; // Define log format const logFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json() ); // Console format for development (pretty print) const consoleFormat = winston.format.combine( winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf( (info) => `${info.timestamp} ${info.level}: ${info.message}${info.stack ? '\n' + info.stack : ''}` ) ); // Define transports const transports = [ // Console output new winston.transports.Console({ format: consoleFormat, handleExceptions: true, }), // Error logs - rotate daily new DailyRotateFile({ filename: path.join(__dirname, '../../logs/error-%DATE%.log'), datePattern: 'YYYY-MM-DD', level: 'error', format: logFormat, maxSize: '20m', maxFiles: '14d', handleExceptions: true, }), // Combined logs - rotate daily new DailyRotateFile({ filename: path.join(__dirname, '../../logs/combined-%DATE%.log'), datePattern: 'YYYY-MM-DD', format: logFormat, maxSize: '20m', maxFiles: '14d', }), // HTTP logs - rotate daily new DailyRotateFile({ filename: path.join(__dirname, '../../logs/http-%DATE%.log'), datePattern: 'YYYY-MM-DD', level: 'http', format: logFormat, maxSize: '20m', maxFiles: '7d', }), ]; // Create the logger const logger = winston.createLogger({ level: level(), levels, format: logFormat, transports, exitOnError: false, }); // Create a stream object for Morgan HTTP logger logger.stream = { write: (message) => { logger.http(message.trim()); }, }; // Helper methods for common logging patterns logger.logRequest = (req, res, duration) => { const logData = { method: req.method, url: req.url, status: res.statusCode, duration: `${duration}ms`, ip: req.ip, userAgent: req.get('user-agent'), }; if (res.statusCode >= 500) { logger.error('Request failed', logData); } else if (res.statusCode >= 400) { logger.warn('Request error', logData); } else { logger.http('Request completed', logData); } }; logger.logEmailSent = (recipient, campaign, status) => { logger.info('Email sent', { event: 'email_sent', recipient, campaign, status, timestamp: new Date().toISOString(), }); }; logger.logEmailFailed = (recipient, campaign, error) => { logger.error('Email failed', { event: 'email_failed', recipient, campaign, error: error.message, stack: error.stack, timestamp: new Date().toISOString(), }); }; logger.logAuth = (action, username, success, ip) => { const level = success ? 'info' : 'warn'; logger[level]('Auth action', { event: 'auth', action, username, success, ip, timestamp: new Date().toISOString(), }); }; logger.logCampaignAction = (action, campaignId, userId, ip) => { logger.info('Campaign action', { event: 'campaign_action', action, campaignId, userId, ip, timestamp: new Date().toISOString(), }); }; logger.logRateLimitHit = (endpoint, ip, limit) => { logger.warn('Rate limit hit', { event: 'rate_limit', endpoint, ip, limit, timestamp: new Date().toISOString(), }); }; logger.logHealthCheck = (service, status, details = {}) => { const level = status === 'healthy' ? 'info' : 'error'; logger[level]('Health check', { event: 'health_check', service, status, ...details, timestamp: new Date().toISOString(), }); }; module.exports = logger;