From 232755776484c1300876acf442b23b83b2047bf4 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Thu, 29 Jan 2026 13:19:00 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update=20logger=20utili?= =?UTF-8?q?ty=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokedex.online/server/utils/logger.js | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 code/websites/pokedex.online/server/utils/logger.js diff --git a/code/websites/pokedex.online/server/utils/logger.js b/code/websites/pokedex.online/server/utils/logger.js new file mode 100644 index 0000000..ccc5088 --- /dev/null +++ b/code/websites/pokedex.online/server/utils/logger.js @@ -0,0 +1,150 @@ +/** + * 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;