import { ConsoleLogger } from '@nestjs/common'; import { Subject } from 'rxjs'; export type LogEntry = { timestamp: string; level: 'log' | 'error' | 'warn' | 'debug' | 'verbose'; context: string; message: string; }; // Singleton — created once in main.ts, accessed by the SSE controller // via LogStreamService.instance. NestJS DI isn't available at bootstrap // time (the logger is created before the container), so we use a static // instance instead of @Injectable(). export class LogStreamService extends ConsoleLogger { static readonly instance = new LogStreamService(); readonly logSubject = new Subject(); private readonly buffer: LogEntry[] = []; private static readonly MAX_BUFFER = 500; getRecentLogs(limit = 200): LogEntry[] { return this.buffer.slice(-limit); } private emit(level: LogEntry['level'], message: unknown, context?: string) { const entry: LogEntry = { timestamp: new Date().toISOString(), level, context: context ?? this.context ?? '', message: typeof message === 'string' ? message : JSON.stringify(message), }; this.buffer.push(entry); if (this.buffer.length > LogStreamService.MAX_BUFFER) this.buffer.shift(); this.logSubject.next(entry); } log(message: unknown, context?: string) { super.log(message, context); this.emit('log', message, context); } error(message: unknown, stack?: string, context?: string) { super.error(message, stack, context); this.emit('error', message, context); } warn(message: unknown, context?: string) { super.warn(message, context); this.emit('warn', message, context); } debug(message: unknown, context?: string) { super.debug(message, context); this.emit('debug', message, context); } verbose(message: unknown, context?: string) { super.verbose(message, context); this.emit('verbose', message, context); } }