import { EventEmitter } from 'events'; import { randomUUID } from 'crypto'; export type LogLevel = 'info' | 'warn' | 'error' | 'debug'; export interface LogEntry { id: string; timestamp: string; level: LogLevel; source: string; message: string; meta?: Record; } interface FilterOptions { limit?: number; level?: LogLevel; source?: string; } class LogStream extends EventEmitter { private buffer: LogEntry[] = []; private readonly maxEntries = 500; push(entry: Omit & { id?: string; timestamp?: string }): void { const normalized: LogEntry = { id: entry.id || randomUUID(), timestamp: entry.timestamp || new Date().toISOString(), level: entry.level, source: entry.source || 'backend', message: entry.message, meta: entry.meta, }; this.buffer.unshift(normalized); if (this.buffer.length > this.maxEntries) { this.buffer.pop(); } this.emit('log', normalized); } getRecent(options: FilterOptions = {}): LogEntry[] { const { limit = 100, level, source } = options; const normalizedLimit = Math.min(Math.max(limit, 1), this.maxEntries); return this.buffer .filter((entry) => { if (level && entry.level !== level) return false; if (source && entry.source !== source) return false; return true; }) .slice(0, normalizedLimit); } getSources(): string[] { const sources = new Set(); this.buffer.forEach((entry) => sources.add(entry.source)); return Array.from(sources); } } export const logStream = new LogStream(); export type LogStreamListener = (entry: LogEntry) => void;