From 0020a00943a2c35c15883c76511fbeb2c65962c0 Mon Sep 17 00:00:00 2001 From: gronod Date: Sat, 29 Aug 2026 17:47:56 +0100 Subject: [PATCH] feat(logging): integrate tauri-plugin-log with rotating logs and settings UI (resolves #139) --- src-tauri/Cargo.toml | 2 ++ src-tauri/src/commands.rs | 45 +++++++++++++++++++++++++ src-tauri/src/lib.rs | 19 +++++++++++ src-tauri/src/process_manager.rs | 6 ++++ src-tauri/src/settings.rs | 1 + src/index.html | 20 +++++++++++ src/js/settings.js | 57 +++++++++++++++++++++++++++++--- 7 files changed, 146 insertions(+), 4 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5b68d70..dd3b4fa 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,6 +23,8 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["process", "io-util", "sync", "time", "rt-multi-thread", "macros"] } tauri-plugin-dialog = "2.7.2" +tauri-plugin-log = "2" +log = "0.4" base64 = "0.22" image = { version = "0.25", default-features = false, features = ["png", "tiff"] } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index b102778..bc9a9c3 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -121,6 +121,51 @@ pub fn get_default_working_dir(app: AppHandle) -> Result { resolve_safe_cwd(&app, "") } +#[tauri::command] +pub fn get_log_path(app: AppHandle) -> Result { + let log_dir = app + .path() + .app_log_dir() + .map_err(|e| format!("Failed to resolve log dir: {}", e))?; + Ok(log_dir.join("iccery.log").to_string_lossy().to_string()) +} + +#[tauri::command] +pub fn open_log_dir(app: AppHandle) -> Result<(), String> { + let log_dir = app + .path() + .app_log_dir() + .map_err(|e| format!("Failed to resolve log dir: {}", e))?; + + std::fs::create_dir_all(&log_dir).map_err(|e| format!("Failed to create log dir: {}", e))?; + + #[cfg(target_os = "windows")] + { + std::process::Command::new("explorer") + .arg(&log_dir) + .spawn() + .map_err(|e| format!("Failed to open log folder: {}", e))?; + } + + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg(&log_dir) + .spawn() + .map_err(|e| format!("Failed to open log folder: {}", e))?; + } + + #[cfg(target_os = "linux")] + { + std::process::Command::new("xdg-open") + .arg(&log_dir) + .spawn() + .map_err(|e| format!("Failed to open log folder: {}", e))?; + } + + Ok(()) +} + #[tauri::command] pub async fn select_target_file( app: AppHandle, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 42e5884..037fd7b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,12 +8,31 @@ mod settings; pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) + .plugin( + tauri_plugin_log::Builder::default() + .targets([ + tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir { + file_name: Some("iccery".into()), + }), + tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout), + tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Webview), + ]) + .level(if cfg!(debug_assertions) { + log::LevelFilter::Debug + } else { + log::LevelFilter::Info + }) + .rotation_strategy(tauri_plugin_log::RotationStrategy::KeepOne) + .build(), + ) .manage(process_manager::ProcessManager::new()) .manage(print::PrinterDevModeStore::new()) .invoke_handler(tauri::generate_handler![ commands::spawn_process, commands::get_app_info, commands::get_default_working_dir, + commands::get_log_path, + commands::open_log_dir, commands::select_target_file, commands::select_directory, commands::send_stdin, diff --git a/src-tauri/src/process_manager.rs b/src-tauri/src/process_manager.rs index e956cd0..7868542 100644 --- a/src-tauri/src/process_manager.rs +++ b/src-tauri/src/process_manager.rs @@ -51,6 +51,7 @@ impl ProcessManager { command.creation_flags(CREATE_NO_WINDOW); } + log::info!(target: "subprocess", "Spawning process '{id}': {binary} {:?}", args); match command.spawn() { Ok(mut child) => { let stdout = child.stdout.take().expect("Failed to open stdout"); @@ -67,6 +68,7 @@ impl ProcessManager { let json_str = line[JSON_ROW_PREFIX.len()..].to_string(); crate::events::emit_json_row(&app_clone, &id_clone, json_str); } else { + log::info!(target: "subprocess", "[{id_clone}] {line}"); emit_stdout(&app_clone, &id_clone, line); } } @@ -77,6 +79,7 @@ impl ProcessManager { tokio::spawn(async move { let mut reader = BufReader::new(stderr).lines(); while let Ok(Some(line)) = reader.next_line().await { + log::warn!(target: "subprocess", "[{id_clone2}] [stderr] {line}"); emit_stderr(&app_clone2, &id_clone2, line); } }); @@ -112,6 +115,8 @@ impl ProcessManager { } }; + log::info!(target: "subprocess", "Process '{id_clone_exit}' exited with code {exit_code}"); + // Reap child from process manager maps upon exit { let mut stdins = stdins_clone.lock().await; @@ -126,6 +131,7 @@ impl ProcessManager { Ok(()) } Err(e) => { + log::error!(target: "subprocess", "Failed to spawn '{id}': {e}"); emit_error(&app, &id, e.to_string()); Err(e.to_string()) } diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index c45a2a9..3bbf72b 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -24,6 +24,7 @@ pub struct ProfilingPreset { pub struct AppSettings { pub argyll_binary_dir: Option, pub default_instrument: Option, + pub log_level: Option, #[serde(default)] pub custom_presets: Vec, } diff --git a/src/index.html b/src/index.html index 6c5ce1e..53f018f 100644 --- a/src/index.html +++ b/src/index.html @@ -489,6 +489,26 @@ +
+ +
+
+ + +
+
+ + +
+
+ +