diff --git a/backend/package.json b/backend/package.json index c000e67..a29fc55 100644 --- a/backend/package.json +++ b/backend/package.json @@ -31,6 +31,7 @@ "pg": "^8.14.0", "redis": "^4.7.0", "uuid": "^11.1.0", + "winston": "^3.17.0", "yargs": "^17.7.2" }, "devDependencies": { diff --git a/backend/src/utils/logger.ts b/backend/src/utils/logger.ts index 34c7f50..b3fb9f4 100644 --- a/backend/src/utils/logger.ts +++ b/backend/src/utils/logger.ts @@ -1,100 +1,114 @@ +import winston from 'winston'; import fs from 'fs'; import path from 'path'; import config from '../config'; -/** - * Logger levels - */ -export enum LogLevel { - DEBUG = 'DEBUG', - INFO = 'INFO', - WARN = 'WARN', - ERROR = 'ERROR' +// Define log levels matching existing implementation +const levels = { + error: 0, + warn: 1, + info: 2, + debug: 3 +}; + +// Create logs directory if it doesn't exist +const logDir = path.join(process.cwd(), 'logs'); +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); } +// Set log file path with date in filename +const now = new Date(); +const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; +const logFile = path.join(logDir, `app-${dateStr}.log`); + +// Define log format +const logFormat = winston.format.printf(({ timestamp, level, message, ...metadata }) => { + let logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`; + + // Add metadata if exists and is not empty + if (metadata && Object.keys(metadata).length > 0) { + try { + logMessage += ` - ${JSON.stringify(metadata)}`; + } catch (error) { + logMessage += ` - [Object cannot be stringified]`; + } + } + + return logMessage; +}); + +// Create Winston logger +const winstonLogger = winston.createLogger({ + levels, + level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + logFormat + ), + transports: [ + // Console transport + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + logFormat + ) + }), + // File transport + new winston.transports.File({ + filename: logFile + }) + ], + exitOnError: false +}); + /** - * Utility class for logging messages to console and file + * Logger interface that matches the previous implementation */ class Logger { - private logDir: string; private logFile: string; private debugEnabled: boolean; constructor() { - // Set up log directory - this.logDir = path.join(process.cwd(), 'logs'); - - // Create logs directory if it doesn't exist - if (!fs.existsSync(this.logDir)) { - fs.mkdirSync(this.logDir, { recursive: true }); - } - - // Set log file path with date in filename - const now = new Date(); - const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; - this.logFile = path.join(this.logDir, `app-${dateStr}.log`); - - // Enable debug logs based on environment (NODE_ENV) + this.logFile = logFile; this.debugEnabled = process.env.NODE_ENV !== 'production'; } - /** - * Format log message with timestamp and level - */ - private formatMessage(level: LogLevel, message: string, data?: any): string { - const timestamp = new Date().toISOString(); - let formattedMessage = `[${timestamp}] [${level}] ${message}`; - - if (data) { - if (typeof data === 'object') { - try { - const dataStr = JSON.stringify(data); - formattedMessage += ` - ${dataStr}`; - } catch (error) { - formattedMessage += ` - [Object cannot be stringified]`; - } - } else { - formattedMessage += ` - ${data}`; - } - } - - return formattedMessage; - } - - /** - * Write log to file - */ - private writeToFile(message: string): void { - fs.appendFileSync(this.logFile, message + '\n'); - } - /** * Debug level log */ debug(message: string, data?: any): void { if (!this.debugEnabled) return; - const formattedMessage = this.formatMessage(LogLevel.DEBUG, message, data); - console.debug(formattedMessage); - this.writeToFile(formattedMessage); + if (data) { + winstonLogger.debug(message, data); + } else { + winstonLogger.debug(message); + } } /** * Info level log */ info(message: string, data?: any): void { - const formattedMessage = this.formatMessage(LogLevel.INFO, message, data); - console.info(formattedMessage); - this.writeToFile(formattedMessage); + if (data) { + winstonLogger.info(message, data); + } else { + winstonLogger.info(message); + } } /** * Warning level log */ warn(message: string, data?: any): void { - const formattedMessage = this.formatMessage(LogLevel.WARN, message, data); - console.warn(formattedMessage); - this.writeToFile(formattedMessage); + if (data) { + winstonLogger.warn(message, data); + } else { + winstonLogger.warn(message); + } } /** @@ -112,9 +126,11 @@ class Logger { }; } - const formattedMessage = this.formatMessage(LogLevel.ERROR, message, errorData); - console.error(formattedMessage); - this.writeToFile(formattedMessage); + if (errorData) { + winstonLogger.error(message, errorData); + } else { + winstonLogger.error(message); + } } /**