LetsTryGPT/agent4-implementation
0
1/**2 * Centralized logging utility3 * Replaces scattered console.log/error/warn statements4 * Provides structured logging with levels and metadata5 */6 7export enum LogLevel {8 DEBUG = 0,9 INFO = 1,10 WARN = 2,11 ERROR = 3,12 NONE = 4,13}14 15export interface LogMeta {16 [key: string]: unknown;17}18 19export interface LogEntry {20 level: keyof typeof LogLevel;21 message: string;22 timestamp: string;23 meta?: LogMeta;24 error?: {25 message: string;26 stack?: string;27 name?: string;28 };29}30 31export class Logger {32 private static instance: Logger;33 private logLevel: LogLevel;34 private enableConsole: boolean;35 private logs: LogEntry[] = [];36 37 private constructor(logLevel: LogLevel = LogLevel.INFO, enableConsole: boolean = true) {38 this.logLevel = logLevel;39 this.enableConsole = enableConsole;40 }41 42 /**43 * Get singleton logger instance44 */45 static getInstance(): Logger {46 if (!Logger.instance) {47 // Read log level from env48 const levelStr = process.env.LOG_LEVEL || 'info';49 const level = Logger.parseLogLevel(levelStr);50 Logger.instance = new Logger(level, process.env.NODE_ENV !== 'test');51 }52 return Logger.instance;53 }54 55 /**56 * Parse log level from string57 */58 private static parseLogLevel(level: string): LogLevel {59 const normalized = level.toUpperCase();60 if (normalized in LogLevel) {61 return LogLevel[normalized as keyof typeof LogLevel];62 }63 return LogLevel.INFO;64 }65 66 /**67 * Set log level68 */69 setLogLevel(level: LogLevel): void {70 this.logLevel = level;71 }72 73 /**74 * Enable/disable console output75 */76 setConsoleOutput(enabled: boolean): void {77 this.enableConsole = enabled;78 }79 80 /**81 * Create log entry82 */83 private createEntry(84 level: keyof typeof LogLevel,85 message: string,86 meta?: LogMeta,87 error?: Error88 ): LogEntry {89 const entry: LogEntry = {90 level,91 message,92 timestamp: new Date().toISOString(),93 };94 95 if (meta) {96 entry.meta = meta;97 }98 99 if (error) {100 entry.error = {101 message: error.message,102 name: error.name,103 stack: error.stack,104 };105 }106 107 return entry;108 }109 110 /**111 * Write log entry112 */113 private write(entry: LogEntry): void {114 const levelValue = LogLevel[entry.level];115 116 if (levelValue < this.logLevel) {117 return; // Skip if below configured log level118 }119 120 // Store in memory (circular buffer, keep last 1000)121 this.logs.push(entry);122 if (this.logs.length > 1000) {123 this.logs.shift();124 }125 126 // Output to console if enabled127 if (this.enableConsole) {128 this.outputToConsole(entry);129 }130 }131 132 /**133 * Output log entry to console134 */135 private outputToConsole(entry: LogEntry): void {136 const prefix = `[${entry.timestamp}] [${entry.level}]`;137 const message = `${prefix} ${entry.message}`;138 139 switch (entry.level) {140 case 'DEBUG':141 case 'INFO':142 console.log(message, entry.meta || '');143 break;144 case 'WARN':145 console.warn(message, entry.meta || '');146 break;147 case 'ERROR':148 console.error(message, entry.error || entry.meta || '');149 if (entry.error?.stack) {150 console.error(entry.error.stack);151 }152 break;153 }154 }155 156 /**157 * Debug level logging158 */159 debug(message: string, meta?: LogMeta): void {160 this.write(this.createEntry('DEBUG', message, meta));161 }162 163 /**164 * Info level logging165 */166 info(message: string, meta?: LogMeta): void {167 this.write(this.createEntry('INFO', message, meta));168 }169 170 /**171 * Warning level logging172 */173 warn(message: string, meta?: LogMeta): void {174 this.write(this.createEntry('WARN', message, meta));175 }176 177 /**178 * Error level logging179 */180 error(message: string, error?: Error, meta?: LogMeta): void {181 this.write(this.createEntry('ERROR', message, meta, error));182 }183 184 /**185 * Get recent logs186 */187 getRecentLogs(count: number = 100): LogEntry[] {188 return this.logs.slice(-count);189 }190 191 /**192 * Clear logs193 */194 clearLogs(): void {195 this.logs = [];196 }197 198 /**199 * Get logs matching criteria200 */201 getLogs(filter?: { level?: keyof typeof LogLevel; since?: Date }): LogEntry[] {202 let filtered = [...this.logs];203 204 if (filter?.level) {205 filtered = filtered.filter((log) => log.level === filter.level);206 }207 208 if (filter?.since) {209 const sinceTime = filter.since.getTime();210 filtered = filtered.filter((log) => new Date(log.timestamp).getTime() >= sinceTime);211 }212 213 return filtered;214 }215}216 217/**218 * Export convenience instance219 */220export const logger = Logger.getInstance();221 222/**223 * Convenience functions224 */225export const debug = (message: string, meta?: LogMeta) => logger.debug(message, meta);226export const info = (message: string, meta?: LogMeta) => logger.info(message, meta);227export const warn = (message: string, meta?: LogMeta) => logger.warn(message, meta);228export const error = (message: string, err?: Error, meta?: LogMeta) =>229 logger.error(message, err, meta);230 