import { getConfig } from '../config/env.js'; /** * Log levels */ export enum LogLevel { DEBUG = 'debug', INFO = 'info', WARN = 'warn', ERROR = 'error' } /** * Logger interface */ export interface Logger { debug(message: string, ...args: any[]): void; info(message: string, ...args: any[]): void; warn(message: string, ...args: any[]): void; error(message: string, ...args: any[]): void; child(fields: Record): Logger; } /** * Console logger implementation */ export class ConsoleLogger implements Logger { private readonly isDevelopment: boolean; private readonly fields: Record; constructor(fields: Record = {}) { this.isDevelopment = getConfig().NODE_ENV === 'development'; this.fields = fields; } /** * Format log message with timestamp and level */ private formatMessage(level: LogLevel, message: string): string { const timestamp = new Date().toISOString(); return `[${timestamp}] [${level.toUpperCase()}] ${message}`; } /** * Create a child logger with additional fields */ child(fields: Record): Logger { return new ConsoleLogger({ ...this.fields, ...fields }); } /** * Log a debug message (only in development) */ debug(message: string, ...args: any[]): void { if (this.isDevelopment) { console.debug(this.formatMessage(LogLevel.DEBUG, message), ...this.processArgs(args)); } } /** * Log an info message */ info(message: string, ...args: any[]): void { console.info(this.formatMessage(LogLevel.INFO, message), ...this.processArgs(args)); } /** * Log a warning message */ warn(message: string, ...args: any[]): void { console.warn(this.formatMessage(LogLevel.WARN, message), ...this.processArgs(args)); } /** * Log an error message */ error(message: string, ...args: any[]): void { console.error(this.formatMessage(LogLevel.ERROR, message), ...this.processArgs(args)); } /** * Process args and add fields */ private processArgs(args: any[]): any[] { if (Object.keys(this.fields).length === 0) { return args; } // If the first arg is an object, merge fields into it if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) { args[0] = { ...args[0], ...this.fields }; return args; } // Otherwise, add fields as a new object return [this.fields, ...args]; } } // Create singleton instance let logger: Logger | null = null; /** * Get the logger instance * @returns Logger instance */ export function getLogger(): Logger { if (!logger) { logger = new ConsoleLogger(); } return logger; } /** * Set a custom logger implementation * @param customLogger The custom logger to use */ export function setLogger(customLogger: Logger): void { logger = customLogger; }