From a8738f27bbc8284249932d9589bfe927d1bfacc5 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Thu, 15 Jan 2026 16:57:10 +0800 Subject: feat: Implement logging system with game log parsing and event listeners for enhanced log management --- ui/src/stores/logs.svelte.ts | 139 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 ui/src/stores/logs.svelte.ts (limited to 'ui/src') diff --git a/ui/src/stores/logs.svelte.ts b/ui/src/stores/logs.svelte.ts new file mode 100644 index 0000000..5491f70 --- /dev/null +++ b/ui/src/stores/logs.svelte.ts @@ -0,0 +1,139 @@ +import { listen } from "@tauri-apps/api/event"; + +export interface LogEntry { + id: number; + timestamp: string; + level: "info" | "warn" | "error" | "debug" | "fatal"; + source: string; + message: string; +} + +// Parse Minecraft/Java log format: [HH:MM:SS] [Thread/LEVEL]: message +// or: [HH:MM:SS] [Thread/LEVEL] [Source]: message +const GAME_LOG_REGEX = /^\[[\d:]+\]\s*\[([^\]]+)\/(\w+)\](?:\s*\[([^\]]+)\])?:\s*(.*)$/; + +function parseGameLogLevel(levelStr: string): LogEntry["level"] { + const upper = levelStr.toUpperCase(); + if (upper === "INFO") return "info"; + if (upper === "WARN" || upper === "WARNING") return "warn"; + if (upper === "ERROR" || upper === "SEVERE") return "error"; + if (upper === "DEBUG" || upper === "TRACE" || upper === "FINE" || upper === "FINER" || upper === "FINEST") return "debug"; + if (upper === "FATAL") return "fatal"; + return "info"; +} + +export class LogsState { + logs = $state([]); + private nextId = 0; + private maxLogs = 5000; + + // Track all unique sources for filtering + sources = $state>(new Set(["Launcher"])); + + constructor() { + this.addLog("info", "Launcher", "Logs initialized"); + this.setupListeners(); + } + + addLog(level: LogEntry["level"], source: string, message: string) { + const now = new Date(); + const timestamp = now.toLocaleTimeString() + "." + now.getMilliseconds().toString().padStart(3, "0"); + + this.logs.push({ + id: this.nextId++, + timestamp, + level, + source, + message, + }); + + // Track source + if (!this.sources.has(source)) { + this.sources = new Set([...this.sources, source]); + } + + if (this.logs.length > this.maxLogs) { + this.logs.shift(); + } + } + + // Parse game output and extract level/source + addGameLog(rawLine: string, isStderr: boolean) { + const match = rawLine.match(GAME_LOG_REGEX); + + if (match) { + const [, thread, levelStr, extraSource, message] = match; + const level = parseGameLogLevel(levelStr); + // Use extraSource if available, otherwise use thread name as source hint + const source = extraSource || `Game/${thread.split("-")[0]}`; + this.addLog(level, source, message); + } else { + // Fallback: couldn't parse, use stderr as error indicator + const level = isStderr ? "error" : "info"; + this.addLog(level, "Game", rawLine); + } + } + + clear() { + this.logs = []; + this.sources = new Set(["Launcher"]); + this.addLog("info", "Launcher", "Logs cleared"); + } + + // Export with filter support + exportLogs(filteredLogs: LogEntry[]): string { + return filteredLogs + .map((l) => `[${l.timestamp}] [${l.source}/${l.level.toUpperCase()}] ${l.message}`) + .join("\n"); + } + + private async setupListeners() { + // General Launcher Logs + await listen("launcher-log", (e) => { + this.addLog("info", "Launcher", e.payload); + }); + + // Game Stdout - parse log level + await listen("game-stdout", (e) => { + this.addGameLog(e.payload, false); + }); + + // Game Stderr - parse log level, default to error + await listen("game-stderr", (e) => { + this.addGameLog(e.payload, true); + }); + + // Download Events (Summarized) + await listen("download-start", (e) => { + this.addLog("info", "Downloader", `Starting batch download of ${e.payload} files...`); + }); + + await listen("download-complete", () => { + this.addLog("info", "Downloader", "All downloads completed."); + }); + + // Listen to file download progress to log finished files + await listen("download-progress", (e) => { + const p = e.payload; + if (p.status === "Finished") { + if (p.file.endsWith(".jar")) { + this.addLog("info", "Downloader", `Downloaded ${p.file}`); + } + } + }); + + // Java Download + await listen("java-download-progress", (e) => { + const p = e.payload; + if (p.status === "Downloading" && p.percentage === 0) { + this.addLog("info", "JavaInstaller", `Downloading Java: ${p.file_name}`); + } else if (p.status === "Completed") { + this.addLog("info", "JavaInstaller", `Java installed: ${p.file_name}`); + } else if (p.status === "Error") { + this.addLog("error", "JavaInstaller", `Java download error`); + } + }); + } +} + +export const logsState = new LogsState(); -- cgit v1.2.3-70-g09d2