import { writable, derived } from "svelte/store"; import { listen } from "@tauri-apps/api/event"; export type LogLevel = "debug" | "info" | "warn" | "error"; export interface LogEntry { id: string; timestamp: Date; level: LogLevel; message: string; source: "frontend" | "backend"; } interface DebugConsoleState { logs: LogEntry[]; isOpen: boolean; maxLogs: number; filterLevel: LogLevel | "all"; autoScroll: boolean; } const MAX_LOGS = 1000; // Circular buffer size function createDebugConsoleStore() { const { subscribe, update } = writable({ logs: [], isOpen: false, maxLogs: MAX_LOGS, filterLevel: "all", autoScroll: true, }); let logCounter = 0; function addLog(level: LogLevel, message: string, source: "frontend" | "backend") { update((state) => { const newLog: LogEntry = { id: `log-${Date.now()}-${logCounter++}`, timestamp: new Date(), level, message, source, }; const updatedLogs = [...state.logs, newLog]; // Implement circular buffer - remove oldest if exceeding max if (updatedLogs.length > state.maxLogs) { updatedLogs.shift(); } return { ...state, logs: updatedLogs }; }); } // Override console methods to capture frontend logs const originalConsole = { log: console.log, info: console.info, warn: console.warn, error: console.error, debug: console.debug, }; function setupConsoleCapture() { console.log = (...args: unknown[]) => { originalConsole.log(...args); addLog("info", args.map((arg) => String(arg)).join(" "), "frontend"); }; console.info = (...args: unknown[]) => { originalConsole.info(...args); addLog("info", args.map((arg) => String(arg)).join(" "), "frontend"); }; console.warn = (...args: unknown[]) => { originalConsole.warn(...args); addLog("warn", args.map((arg) => String(arg)).join(" "), "frontend"); }; console.error = (...args: unknown[]) => { originalConsole.error(...args); addLog("error", args.map((arg) => String(arg)).join(" "), "frontend"); }; console.debug = (...args: unknown[]) => { originalConsole.debug(...args); addLog("debug", args.map((arg) => String(arg)).join(" "), "frontend"); }; // Capture unhandled errors window.addEventListener("error", (event) => { addLog( "error", `[Unhandled Error] ${event.message} at ${event.filename}:${event.lineno}:${event.colno}`, "frontend" ); }); // Capture unhandled promise rejections window.addEventListener("unhandledrejection", (event) => { addLog("error", `[Unhandled Promise Rejection] ${event.reason}`, "frontend"); }); } function restoreConsole() { console.log = originalConsole.log; console.info = originalConsole.info; console.warn = originalConsole.warn; console.error = originalConsole.error; console.debug = originalConsole.debug; } // Listen for backend logs async function setupBackendLogsListener() { await listen<{ level: LogLevel; message: string }>("debug:log", (event) => { addLog(event.payload.level, event.payload.message, "backend"); }); } return { subscribe, toggle: () => update((state) => ({ ...state, isOpen: !state.isOpen })), open: () => update((state) => ({ ...state, isOpen: true })), close: () => update((state) => ({ ...state, isOpen: false })), clear: () => update((state) => ({ ...state, logs: [] })), setFilterLevel: (level: LogLevel | "all") => update((state) => ({ ...state, filterLevel: level })), setAutoScroll: (enabled: boolean) => update((state) => ({ ...state, autoScroll: enabled })), setupConsoleCapture, restoreConsole, setupBackendLogsListener, }; } export const debugConsoleStore = createDebugConsoleStore(); // Derived store for filtered logs export const filteredLogs = derived(debugConsoleStore, ($debugConsole) => { if ($debugConsole.filterLevel === "all") { return $debugConsole.logs; } const levelPriority: Record = { debug: 0, info: 1, warn: 2, error: 3, }; const minPriority = levelPriority[$debugConsole.filterLevel]; return $debugConsole.logs.filter((log) => levelPriority[log.level] >= minPriority); });