From 5b8ae63de19502c3e92a9781cab7e7268bdd4fb4 Mon Sep 17 00:00:00 2001 From: Hikari Date: Fri, 6 Feb 2026 20:16:26 -0800 Subject: [PATCH] feat: add debug console for frontend and backend logs Added a comprehensive debug console feature that captures and displays logs from both the frontend and backend in a unified interface. Frontend changes: - Created DebugConsole component with real-time log display - Added debugConsoleStore for state management and console capture - Integrated console into main layout - Added toggle button in StatusBar with console icon - Implemented Ctrl+` keyboard shortcut to open/close console - Features: log filtering by level, auto-scroll, timestamps, colour-coding Backend changes: - Added tracing and tracing-subscriber dependencies - Created custom TauriLogLayer to emit Rust logs to frontend - Integrated tracing subscriber in lib.rs setup - Logs are forwarded via Tauri events (debug:log) Key features: - Circular buffer (max 1000 logs) prevents memory issues - Frontend logs captured via console method overrides - Backend logs forwarded from Rust tracing layer - Log level filtering (debug, info, warn, error, all) - Source badges distinguish frontend vs backend logs - Colour-coded log levels for easy identification - Auto-scroll toggle for inspecting older logs - Clear logs button for resetting the console - Beautiful dark-themed UI matching app aesthetic Closes #126 --- src-tauri/Cargo.lock | 74 ++++++ src-tauri/Cargo.toml | 2 + src-tauri/src/debug_logger.rs | 78 ++++++ src-tauri/src/lib.rs | 15 +- src/lib/components/DebugConsole.svelte | 330 +++++++++++++++++++++++++ src/lib/components/StatusBar.svelte | 15 ++ src/lib/stores/debugConsole.ts | 140 +++++++++++ src/routes/+layout.svelte | 2 + src/routes/+page.svelte | 8 + 9 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 src-tauri/src/debug_logger.rs create mode 100644 src/lib/components/DebugConsole.svelte create mode 100644 src/lib/stores/debugConsole.ts diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9e26d9b..9854341 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1658,6 +1658,8 @@ dependencies = [ "tauri-plugin-store", "tempfile", "tokio", + "tracing", + "tracing-subscriber", "uuid 1.19.0", "windows 0.62.2", ] @@ -2245,6 +2247,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.10" @@ -2401,6 +2412,15 @@ dependencies = [ "zbus", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3879,6 +3899,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shared_child" version = "1.1.1" @@ -4695,6 +4724,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiff" version = "0.10.3" @@ -4986,6 +5024,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -5167,6 +5235,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version-compare" version = "0.2.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index da7a12c..b80943f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,8 @@ semver = "1" chrono = { version = "0.4.43", features = ["serde"] } discord-rich-presence = "0.2" dirs = "5" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } [target.'cfg(windows)'.dependencies] windows = { version = "0.62", features = [ diff --git a/src-tauri/src/debug_logger.rs b/src-tauri/src/debug_logger.rs new file mode 100644 index 0000000..2691505 --- /dev/null +++ b/src-tauri/src/debug_logger.rs @@ -0,0 +1,78 @@ +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tauri::{AppHandle, Emitter}; +use tracing::{Level, Subscriber}; +use tracing_subscriber::layer::{Context, Layer}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DebugLogEvent { + pub level: String, + pub message: String, +} + +#[derive(Clone)] +pub struct TauriLogLayer { + app: Arc, +} + +impl TauriLogLayer { + pub fn new(app: AppHandle) -> Self { + Self { + app: Arc::new(app), + } + } +} + +impl Layer for TauriLogLayer +where + S: Subscriber, +{ + fn on_event( + &self, + event: &tracing::Event<'_>, + _ctx: Context<'_, S>, + ) { + let metadata = event.metadata(); + let level = match *metadata.level() { + Level::ERROR => "error", + Level::WARN => "warn", + Level::INFO => "info", + Level::DEBUG => "debug", + Level::TRACE => "debug", + }; + + // Extract message from the event + struct MessageVisitor { + message: String, + } + + impl tracing::field::Visit for MessageVisitor { + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + if field.name() == "message" { + self.message = format!("{:?}", value); + } + } + } + + let mut visitor = MessageVisitor { + message: String::new(), + }; + event.record(&mut visitor); + + // If we couldn't extract a message, try to format the whole event + if visitor.message.is_empty() { + visitor.message = metadata.name().to_string(); + } + + // Strip quotes from the message + let message = visitor.message.trim_matches('"').to_string(); + + let log_event = DebugLogEvent { + level: level.to_string(), + message, + }; + + // Emit to frontend + let _ = self.app.emit("debug:log", log_event); + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index be8656d..3fe20e9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,6 +4,7 @@ mod clipboard; mod commands; mod config; mod cost_tracking; +mod debug_logger; mod discord_rpc; mod git; mod notifications; @@ -24,6 +25,7 @@ use bridge_manager::create_shared_bridge_manager; use clipboard::*; use commands::load_saved_achievements; use commands::*; +use debug_logger::TauriLogLayer; use discord_rpc::DiscordRpcManager; use git::*; use notifications::*; @@ -33,6 +35,8 @@ use snippets::*; use std::sync::Arc; use tauri::Manager; use temp_manager::create_shared_temp_manager; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; use tray::{setup_tray, should_minimize_to_tray}; use vbs_notification::*; use windows_toast::*; @@ -58,6 +62,13 @@ pub fn run() { .manage(temp_manager.clone()) .manage(discord_rpc.clone()) .setup(move |app| { + // Initialize tracing with custom layer that emits to frontend + let tauri_layer = TauriLogLayer::new(app.handle().clone()); + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(tauri_layer) + .init(); + // Initialize the app handle in the bridge manager bridge_manager.lock().set_app_handle(app.handle().clone()); @@ -67,10 +78,12 @@ pub fn run() { // Clean up any orphaned temp files from previous sessions if let Ok(count) = temp_manager.lock().cleanup_orphaned_files() { if count > 0 { - println!("Cleaned up {} orphaned temp files", count); + tracing::info!("Cleaned up {} orphaned temp files", count); } } + tracing::info!("Hikari Desktop started successfully"); + // Set up system tray if let Err(e) = setup_tray(app.handle()) { eprintln!("Failed to set up system tray: {}", e); diff --git a/src/lib/components/DebugConsole.svelte b/src/lib/components/DebugConsole.svelte new file mode 100644 index 0000000..1cc1ae4 --- /dev/null +++ b/src/lib/components/DebugConsole.svelte @@ -0,0 +1,330 @@ + + +{#if isOpen} +
+
+
+

Debug Console

+
+
+ + + + + +
+ + + +
+
+
+ {#if logs.length === 0} +
No logs yet...
+ {:else} + {#each logs as log (log.id)} +
+ {formatTimestamp(log.timestamp)} + + [{log.level.toUpperCase()}] + + + {log.source} + + {log.message} +
+ {/each} + {/if} +
+
+
+{/if} + + diff --git a/src/lib/components/StatusBar.svelte b/src/lib/components/StatusBar.svelte index 2e6451f..4c4416d 100644 --- a/src/lib/components/StatusBar.svelte +++ b/src/lib/components/StatusBar.svelte @@ -33,6 +33,7 @@ sanitizeForJson, } from "$lib/utils/conversationUtils"; import { updateDiscordRpc, setSkipNextGreeting } from "$lib/tauri"; + import { debugConsoleStore } from "$lib/stores/debugConsole"; const DISCORD_URL = "https://chat.nhcarrigan.com"; const DONATE_URL = "https://donate.nhcarrigan.com"; @@ -507,6 +508,20 @@ /> +