/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '../../environments/environment'; interface LogPayload { level: 'debug' | 'info' | 'warn' | 'error'; message: string; context?: string; error?: { name: string; message: string; stack?: string; }; } @Injectable({ providedIn: 'root' }) export class ConsoleLoggerService { private http = inject(HttpClient); private originalConsole = { log: console.log.bind(console), error: console.error.bind(console), warn: console.warn.bind(console), debug: console.debug.bind(console), info: console.info.bind(console) }; /** * Initialises the console override to pipe logs to the API. */ initialise(): void { console.log = (...args: unknown[]) => { this.originalConsole.log(...args); this.sendLog('info', this.formatArgs(args)); }; console.info = (...args: unknown[]) => { this.originalConsole.info(...args); this.sendLog('info', this.formatArgs(args)); }; console.debug = (...args: unknown[]) => { this.originalConsole.debug(...args); this.sendLog('debug', this.formatArgs(args)); }; console.warn = (...args: unknown[]) => { this.originalConsole.warn(...args); this.sendLog('warn', this.formatArgs(args)); }; console.error = (...args: unknown[]) => { this.originalConsole.error(...args); // Check if the first argument is an Error object if (args[0] instanceof Error) { const error = args[0]; this.sendLog('error', error.message, 'Console', { name: error.name, message: error.message, stack: error.stack }); } else { this.sendLog('error', this.formatArgs(args)); } }; // Global error handlers window.addEventListener('error', (event: ErrorEvent) => { this.originalConsole.error('Uncaught Error:', event.error); this.sendLog('error', event.message, 'Window Error', { name: event.error?.name || 'Error', message: event.message, stack: event.error?.stack }); }); window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { this.originalConsole.error('Unhandled Promise Rejection:', event.reason); const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason)); this.sendLog('error', error.message, 'Unhandled Rejection', { name: error.name, message: error.message, stack: error.stack }); }); } private formatArgs(args: unknown[]): string { return args.map(arg => { if (typeof arg === 'string') { return arg; } if (arg instanceof Error) { return `${arg.name}: ${arg.message}`; } try { return JSON.stringify(arg); } catch { return String(arg); } }).join(' '); } private sendLog(level: LogPayload['level'], message: string, context?: string, error?: LogPayload['error']): void { const payload: LogPayload = { level, message, context, error }; this.http.post(`${environment.apiUrl}/log`, payload).subscribe({ error: (err) => { this.originalConsole.error('Failed to send log to API:', err); } }); } }