/** * Logger utility with consistent formatting and log levels * Supports console and Loki logging */ import { loggingConfig } from '@stock-bot/config'; import { LokiClient } from './lokiClient'; // Singleton Loki client let lokiClient: LokiClient | null = null; function getLokiClient(): LokiClient { if (!lokiClient) { lokiClient = new LokiClient(loggingConfig); } return lokiClient; } export class Logger { constructor(private serviceName: string, private level: LogLevel = LogLevel.INFO) {} debug(message: string, ...args: any[]): void { this.log(LogLevel.DEBUG, message, ...args); } info(message: string, ...args: any[]): void { this.log(LogLevel.INFO, message, ...args); } warn(message: string, ...args: any[]): void { this.log(LogLevel.WARN, message, ...args); } error(message: string, ...args: any[]): void { this.log(LogLevel.ERROR, message, ...args); } private log(level: LogLevel, message: string, ...args: any[]): void { if (level < this.level) return; const timestamp = new Date().toISOString(); const levelStr = LogLevel[level].padEnd(5); const formattedArgs = args.length ? this.formatArgs(args) : ''; const fullMessage = `${message}${formattedArgs}`; const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${fullMessage}`; // Console logging if (loggingConfig.console) { switch (level) { case LogLevel.ERROR: console.error(logMessage); break; case LogLevel.WARN: console.warn(logMessage); break; case LogLevel.INFO: console.info(logMessage); break; case LogLevel.DEBUG: default: console.debug(logMessage); break; } } // Loki logging try { const loki = getLokiClient(); loki.log(LogLevel[level].toLowerCase(), fullMessage, this.serviceName); } catch (error) { console.error('Failed to send log to Loki:', error); } } private formatArgs(args: any[]): string { try { return args.map(arg => { if (arg instanceof Error) { return ` ${arg.message}\n${arg.stack}`; } else if (typeof arg === 'object') { return ` ${JSON.stringify(arg)}`; } else { return ` ${arg}`; } }).join(''); } catch (error) { return ` [Error formatting log arguments: ${error}]`; } } setLevel(level: LogLevel): void { this.level = level; } } export enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 } /** * Create a new logger instance */ export function createLogger(serviceName: string, level: LogLevel = LogLevel.INFO): Logger { return new Logger(serviceName, level); }