const winston = require('winston'); const DailyRotateFile = require('winston-daily-rotate-file'); const path = require('path'); // Define log format const logFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json() ); // Console format for development const consoleFormat = winston.format.combine( winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, stack }) => { return stack ? `${timestamp} [${level}]: ${message}\n${stack}` : `${timestamp} [${level}]: ${message}`; }) ); // Create logs directory if it doesn't exist const fs = require('fs'); const logsDir = path.join(__dirname, '../logs'); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir); } // Daily rotate file transport for error logs const errorRotateTransport = new DailyRotateFile({ filename: path.join(logsDir, 'error-%DATE%.log'), datePattern: 'YYYY-MM-DD', level: 'error', maxSize: '20m', maxFiles: '14d', format: logFormat }); // Daily rotate file transport for combined logs const combinedRotateTransport = new DailyRotateFile({ filename: path.join(logsDir, 'combined-%DATE%.log'), datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '30d', format: logFormat }); // Daily rotate file transport for HTTP logs const httpRotateTransport = new DailyRotateFile({ filename: path.join(logsDir, 'http-%DATE%.log'), datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '7d', format: logFormat }); // Create the Winston logger const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: logFormat, defaultMeta: { service: 'interview-quiz-api' }, transports: [ errorRotateTransport, combinedRotateTransport ], exceptionHandlers: [ new winston.transports.File({ filename: path.join(logsDir, 'exceptions.log') }) ], rejectionHandlers: [ new winston.transports.File({ filename: path.join(logsDir, 'rejections.log') }) ] }); // Add console transport in development if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: consoleFormat })); } // HTTP logger for request logging const httpLogger = winston.createLogger({ level: 'http', format: logFormat, defaultMeta: { service: 'interview-quiz-api' }, transports: [httpRotateTransport] }); // Stream for Morgan middleware logger.stream = { write: (message) => { httpLogger.http(message.trim()); } }; // Helper functions for structured logging logger.logRequest = (req, message) => { logger.info(message, { method: req.method, url: req.originalUrl, ip: req.ip, userId: req.user?.id, userAgent: req.get('user-agent') }); }; logger.logError = (error, req = null) => { const errorLog = { message: error.message, stack: error.stack, statusCode: error.statusCode || 500 }; if (req) { errorLog.method = req.method; errorLog.url = req.originalUrl; errorLog.ip = req.ip; errorLog.userId = req.user?.id; errorLog.body = req.body; } logger.error('Application Error', errorLog); }; logger.logDatabaseQuery = (query, duration) => { logger.debug('Database Query', { query, duration: `${duration}ms` }); }; logger.logSecurityEvent = (event, req) => { logger.warn('Security Event', { event, method: req.method, url: req.originalUrl, ip: req.ip, userAgent: req.get('user-agent') }); }; module.exports = logger;