/** * Centralized Debug Logger * Captures console logs, errors, warnings for display in Debug tab */ export interface LogEntry { id: string; timestamp: Date; level: 'log' | 'warn' | 'error' | 'info'; category: string; // e.g., "TTS", "STT", "Chat", "System" message: string; data?: any; } class DebugLogger { private logs: LogEntry[] = []; private maxLogs = 500; // Keep last 500 logs private listeners: Set<(logs: LogEntry[]) => void> = new Set(); // Original console methods private originalConsole = { log: console.log.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), info: console.info.bind(console), }; constructor() { this.interceptConsole(); } /** * Intercept console methods to capture logs */ private interceptConsole() { console.log = (...args: any[]) => { this.originalConsole.log(...args); this.addLog('log', 'System', this.formatArgs(args)); }; console.warn = (...args: any[]) => { this.originalConsole.warn(...args); this.addLog('warn', 'System', this.formatArgs(args)); }; console.error = (...args: any[]) => { this.originalConsole.error(...args); this.addLog('error', 'System', this.formatArgs(args)); }; console.info = (...args: any[]) => { this.originalConsole.info(...args); this.addLog('info', 'System', this.formatArgs(args)); }; } /** * Format console arguments into string */ private formatArgs(args: any[]): string { return args .map(arg => { if (typeof arg === 'object') { try { return JSON.stringify(arg, null, 2); } catch { return String(arg); } } return String(arg); }) .join(' '); } /** * Add a log entry */ private addLog(level: LogEntry['level'], category: string, message: string, data?: any) { const entry: LogEntry = { id: `${Date.now()}-${Math.random()}`, timestamp: new Date(), level, category, message, data, }; this.logs.push(entry); // Trim old logs if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(-this.maxLogs); } // Notify listeners this.notifyListeners(); } /** * Public logging methods with category support */ log(category: string, message: string, data?: any) { this.originalConsole.log(`[${category}]`, message, data || ''); this.addLog('log', category, message, data); } warn(category: string, message: string, data?: any) { this.originalConsole.warn(`[${category}]`, message, data || ''); this.addLog('warn', category, message, data); } error(category: string, message: string, data?: any) { this.originalConsole.error(`[${category}]`, message, data || ''); this.addLog('error', category, message, data); } info(category: string, message: string, data?: any) { this.originalConsole.info(`[${category}]`, message, data || ''); this.addLog('info', category, message, data); } /** * Get all logs */ getLogs(): LogEntry[] { return [...this.logs]; } /** * Get logs by category */ getLogsByCategory(category: string): LogEntry[] { return this.logs.filter(log => log.category === category); } /** * Clear all logs */ clear() { this.logs = []; this.notifyListeners(); } /** * Subscribe to log updates */ subscribe(listener: (logs: LogEntry[]) => void) { this.listeners.add(listener); return () => this.listeners.delete(listener); } /** * Notify all listeners */ private notifyListeners() { this.listeners.forEach(listener => listener(this.getLogs())); } /** * Export logs as text */ exportAsText(): string { return this.logs .map(log => { const time = log.timestamp.toLocaleTimeString(); const level = log.level.toUpperCase().padEnd(5); const category = `[${log.category}]`.padEnd(12); return `${time} ${level} ${category} ${log.message}`; }) .join('\n'); } } // Singleton instance export const debugLogger = new DebugLogger();