/** * Production Logger with Winston * * Provides structured logging for development and production environments. * - Console logging for development with colors * - File logging for production with rotation * - JSON format for production log aggregation */ import winston from 'winston'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const isProduction = process.env.NODE_ENV === 'production'; const logLevel = process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'); /** * Custom format for development console output */ const devFormat = winston.format.combine( winston.format.colorize(), winston.format.timestamp({ format: 'HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, ...meta }) => { let msg = `${timestamp} [${level}] ${message}`; if (Object.keys(meta).length > 0) { msg += ` ${JSON.stringify(meta, null, 2)}`; } return msg; }) ); /** * Format for production (JSON for log aggregation) */ const prodFormat = winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ); /** * Create Winston logger instance */ const logger = winston.createLogger({ level: logLevel, format: isProduction ? prodFormat : devFormat, defaultMeta: { service: 'pokedex-backend', environment: process.env.NODE_ENV }, transports: [] }); // Console transport (always enabled) logger.add( new winston.transports.Console({ format: isProduction ? prodFormat : devFormat }) ); // File transports for production if (isProduction) { // All logs logger.add( new winston.transports.File({ filename: path.join(__dirname, '../logs/combined.log'), maxsize: 5242880, // 5MB maxFiles: 5 }) ); // Error logs logger.add( new winston.transports.File({ filename: path.join(__dirname, '../logs/error.log'), level: 'error', maxsize: 5242880, // 5MB maxFiles: 5 }) ); } /** * Express middleware for HTTP request logging */ export function requestLogger(req, res, next) { const start = Date.now(); // Log request logger.info('HTTP Request', { method: req.method, url: req.url, ip: req.ip, userAgent: req.get('user-agent') }); // Log response when finished res.on('finish', () => { const duration = Date.now() - start; const logLevel = res.statusCode >= 400 ? 'warn' : 'info'; logger[logLevel]('HTTP Response', { method: req.method, url: req.url, status: res.statusCode, duration: `${duration}ms` }); }); next(); } /** * Express error logging middleware */ export function errorLogger(err, req, res, next) { logger.error('Express Error', { error: err.message, stack: err.stack, method: req.method, url: req.url, body: req.body }); next(err); } /** * Log uncaught exceptions and unhandled rejections */ process.on('uncaughtException', error => { logger.error('Uncaught Exception', { error: error.message, stack: error.stack }); // Give logger time to write before exiting setTimeout(() => process.exit(1), 1000); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection', { reason: reason instanceof Error ? reason.message : reason, stack: reason instanceof Error ? reason.stack : undefined }); }); export default logger;