From a15ddf34803e7a2f627cf8d1edfea4e66be74cce Mon Sep 17 00:00:00 2001 From: Dylann Batisse Date: Thu, 9 Apr 2026 23:23:54 +0200 Subject: [PATCH] fix(logging): use torrent names in instance log labels --- rustatio-core/src/logger.rs | 9 ++--- rustatio-desktop/src/commands/faker.rs | 27 +++++++++---- rustatio-desktop/src/commands/grid.rs | 26 +++++++++---- rustatio-desktop/src/logging.rs | 31 +++++++++++++-- rustatio-server/src/services/lifecycle.rs | 47 ++++++++++++++++++++--- rustatio-server/src/services/scheduler.rs | 10 ++++- rustatio-server/src/services/state.rs | 2 +- rustatio-wasm/src/lib.rs | 6 +-- 8 files changed, 124 insertions(+), 34 deletions(-) diff --git a/rustatio-core/src/logger.rs b/rustatio-core/src/logger.rs index a2b61f8..ff5f278 100644 --- a/rustatio-core/src/logger.rs +++ b/rustatio-core/src/logger.rs @@ -24,9 +24,8 @@ pub fn set_instance_context(instance_id: Option) { /// Get the current instance context fn get_instance_prefix() -> String { - INSTANCE_CONTEXT.with(|ctx| { - ctx.borrow().as_ref().map_or_else(String::new, |id| format!("[Instance {id}] ")) - }) + INSTANCE_CONTEXT + .with(|ctx| ctx.borrow().as_ref().map_or_else(String::new, |label| format!("[{label}] "))) } #[cfg(all(not(target_arch = "wasm32"), feature = "desktop"))] @@ -310,7 +309,7 @@ mod tests { #[test] fn test_instance_context_str_prefix() { set_instance_context_str(Some("abc")); - assert_eq!(get_instance_prefix(), "[Instance abc] "); + assert_eq!(get_instance_prefix(), "[abc] "); set_instance_context_str(None); assert_eq!(get_instance_prefix(), ""); } @@ -318,7 +317,7 @@ mod tests { #[test] fn test_instance_context_prefix() { set_instance_context(Some(7)); - assert_eq!(get_instance_prefix(), "[Instance 7] "); + assert_eq!(get_instance_prefix(), "[7] "); set_instance_context(None); assert_eq!(get_instance_prefix(), ""); } diff --git a/rustatio-desktop/src/commands/faker.rs b/rustatio-desktop/src/commands/faker.rs index 42657df..3d2b457 100644 --- a/rustatio-desktop/src/commands/faker.rs +++ b/rustatio-desktop/src/commands/faker.rs @@ -7,6 +7,19 @@ use tauri::{AppHandle, State}; use crate::logging::log_and_emit; use crate::state::{AppState, FakerInstance}; +fn set_instance_label(state: &AppState, instance_id: u32, fallback: Option<&str>) { + let label = state + .fakers + .try_read() + .ok() + .and_then(|fakers| fakers.get(&instance_id).map(|instance| instance.summary.name.clone())) + .filter(|name| !name.is_empty()) + .or_else(|| fallback.map(std::string::ToString::to_string)) + .unwrap_or_else(|| instance_id.to_string()); + + rustatio_core::logger::set_instance_context_str(Some(&label)); +} + #[tauri::command] pub async fn start_faker( instance_id: u32, @@ -46,7 +59,7 @@ pub async fn start_faker( let torrent_info_hash = torrent.info_hash; - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, Some(&torrent.name)); // Check if instance already exists (restarting) - preserve cumulative stats let mut config_with_cumulative = config.clone(); @@ -137,7 +150,7 @@ pub async fn stop_faker( app: AppHandle, ) -> Result<(), String> { log_and_emit!(&app, instance_id, info, "Stopping faker"); - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, None); // Clone the Arc under read lock, then drop the HashMap lock let faker = { @@ -181,7 +194,7 @@ pub async fn stop_faker( #[tauri::command] pub async fn update_faker(instance_id: u32, state: State<'_, AppState>) -> Result<(), String> { - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, None); let faker = { let fakers = state.fakers.read().await; @@ -200,7 +213,7 @@ pub async fn update_stats_only( instance_id: u32, state: State<'_, AppState>, ) -> Result { - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, None); let faker = { let fakers = state.fakers.read().await; @@ -233,7 +246,7 @@ pub async fn scrape_tracker( instance_id: u32, state: State<'_, AppState>, ) -> Result<(i64, i64, i64), String> { - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, None); let faker = { let fakers = state.fakers.read().await; @@ -254,7 +267,7 @@ pub async fn pause_faker( app: AppHandle, ) -> Result<(), String> { log_and_emit!(&app, instance_id, info, "Pausing faker"); - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, None); let faker = { let fakers = state.fakers.read().await; @@ -278,7 +291,7 @@ pub async fn resume_faker( app: AppHandle, ) -> Result<(), String> { log_and_emit!(&app, instance_id, info, "Resuming faker"); - rustatio_core::logger::set_instance_context(Some(instance_id)); + set_instance_label(&state, instance_id, None); let faker = { let fakers = state.fakers.read().await; diff --git a/rustatio-desktop/src/commands/grid.rs b/rustatio-desktop/src/commands/grid.rs index 7166d58..0177dda 100644 --- a/rustatio-desktop/src/commands/grid.rs +++ b/rustatio-desktop/src/commands/grid.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use tauri::{AppHandle, State}; use tokio::task::JoinSet; -use crate::logging::log_and_emit; +use crate::logging::{format_instance_message, log_and_emit}; use crate::state::{hex_info_hash, now_secs, AppState, FakerInstance}; use rustatio_watch::InstanceSource; @@ -256,7 +256,7 @@ pub async fn grid_import_files( pub async fn grid_start( ids: Vec, state: State<'_, AppState>, - _app: AppHandle, + app: AppHandle, ) -> Result { let mut succeeded = Vec::new(); let mut failed = Vec::new(); @@ -281,6 +281,7 @@ pub async fn grid_start( } // Spawn HTTP announces in background — return immediately + let app_handle = app.clone(); tauri::async_runtime::spawn(async move { let mut join_set = JoinSet::new(); for (id, faker) in to_start { @@ -292,8 +293,14 @@ pub async fn grid_start( while let Some(result) = join_set.join_next().await { match result { - Ok((id, Ok(()))) => log::info!("[Instance {id}] Started via grid action"), - Ok((id, Err(e))) => log::error!("[Instance {id}] Grid start failed: {e}"), + Ok((id, Ok(()))) => log::info!( + "{}", + format_instance_message(&app_handle, id, "Started via grid action") + ), + Ok((id, Err(e))) => { + let msg = format!("Grid start failed: {e}"); + log::error!("{}", format_instance_message(&app_handle, id, &msg)); + } Err(e) => log::error!("Grid start join error: {e}"), } } @@ -306,7 +313,7 @@ pub async fn grid_start( pub async fn grid_stop( ids: Vec, state: State<'_, AppState>, - _app: AppHandle, + app: AppHandle, ) -> Result { let mut succeeded = Vec::new(); let mut failed = Vec::new(); @@ -332,6 +339,7 @@ pub async fn grid_stop( // Spawn HTTP stop announces + cumulative stats update in background let fakers_arc = Arc::clone(&state.fakers); + let app_handle = app.clone(); tauri::async_runtime::spawn(async move { let mut join_set = JoinSet::new(); for (id, faker) in to_stop { @@ -346,11 +354,15 @@ pub async fn grid_stop( while let Some(result) = join_set.join_next().await { match result { Ok((id, stats, Ok(()))) => { - log::info!("[Instance {id}] Stopped via grid action"); + log::info!( + "{}", + format_instance_message(&app_handle, id, "Stopped via grid action") + ); stats_updates.push((id, stats)); } Ok((id, _, Err(e))) => { - log::error!("[Instance {id}] Grid stop failed: {e}"); + let msg = format!("Grid stop failed: {e}"); + log::error!("{}", format_instance_message(&app_handle, id, &msg)); } Err(e) => log::error!("Grid stop join error: {e}"), } diff --git a/rustatio-desktop/src/logging.rs b/rustatio-desktop/src/logging.rs index 6b67cdb..80b2cca 100644 --- a/rustatio-desktop/src/logging.rs +++ b/rustatio-desktop/src/logging.rs @@ -1,5 +1,7 @@ use serde::Serialize; -use tauri::{AppHandle, Emitter}; +use tauri::{AppHandle, Emitter, Manager}; + +use crate::state::AppState; #[derive(Clone, Serialize)] pub struct LogEvent { @@ -23,6 +25,24 @@ pub fn emit_log(app: &AppHandle, level: &str, message: String) { let _ = app.emit("log-event", log_event); } +pub fn resolve_instance_label(app: &AppHandle, instance_id: u32) -> String { + let state = app.state::(); + let Ok(fakers) = state.fakers.try_read() else { + return instance_id.to_string(); + }; + + fakers + .get(&instance_id) + .map(|instance| instance.summary.name.clone()) + .filter(|name| !name.is_empty()) + .unwrap_or_else(|| instance_id.to_string()) +} + +pub fn format_instance_message(app: &AppHandle, instance_id: u32, message: &str) -> String { + let label = resolve_instance_label(app, instance_id); + format!("[{label}] {message}") +} + macro_rules! log_and_emit { ($app:expr, info, $($arg:tt)*) => { { @@ -40,14 +60,16 @@ macro_rules! log_and_emit { }; ($app:expr, $instance_id:expr, info, $($arg:tt)*) => { { - let msg = format!("[Instance {}] {}", $instance_id, format!($($arg)*)); + let msg_body = format!($($arg)*); + let msg = $crate::logging::format_instance_message($app, $instance_id, &msg_body); log::info!("{}", msg); $crate::logging::emit_log($app, "info", msg); } }; ($app:expr, $instance_id:expr, warn, $($arg:tt)*) => { { - let msg = format!("[Instance {}] {}", $instance_id, format!($($arg)*)); + let msg_body = format!($($arg)*); + let msg = $crate::logging::format_instance_message($app, $instance_id, &msg_body); log::warn!("{}", msg); $crate::logging::emit_log($app, "warn", msg); } @@ -61,7 +83,8 @@ macro_rules! log_and_emit { }; ($app:expr, $instance_id:expr, error, $($arg:tt)*) => { { - let msg = format!("[Instance {}] {}", $instance_id, format!($($arg)*)); + let msg_body = format!($($arg)*); + let msg = $crate::logging::format_instance_message($app, $instance_id, &msg_body); log::error!("{}", msg); $crate::logging::emit_log($app, "error", msg); } diff --git a/rustatio-server/src/services/lifecycle.rs b/rustatio-server/src/services/lifecycle.rs index 8166ef5..b05ad7c 100644 --- a/rustatio-server/src/services/lifecycle.rs +++ b/rustatio-server/src/services/lifecycle.rs @@ -4,6 +4,17 @@ use rustatio_core::logger::set_instance_context_str; use rustatio_core::FakerStats; use std::sync::Arc; +fn resolve_instance_label( + instances: &std::collections::HashMap, + id: &str, +) -> String { + instances + .get(id) + .map(|instance| instance.summary.name.clone()) + .filter(|name| !name.is_empty()) + .unwrap_or_else(|| id.to_string()) +} + #[async_trait] pub trait InstanceLifecycle { async fn start_instance(&self, id: &str) -> Result<(), String>; @@ -17,7 +28,11 @@ pub trait InstanceLifecycle { #[async_trait] impl InstanceLifecycle for AppState { async fn start_instance(&self, id: &str) -> Result<(), String> { - set_instance_context_str(Some(id)); + let label = { + let instances = self.instances.read().await; + resolve_instance_label(&instances, id) + }; + set_instance_context_str(Some(&label)); let (faker, restore) = { let instances = self.instances.read().await; @@ -45,7 +60,11 @@ impl InstanceLifecycle for AppState { } async fn stop_instance(&self, id: &str) -> Result { - set_instance_context_str(Some(id)); + let label = { + let instances = self.instances.read().await; + resolve_instance_label(&instances, id) + }; + set_instance_context_str(Some(&label)); let faker = { let instances = self.instances.read().await; @@ -75,7 +94,11 @@ impl InstanceLifecycle for AppState { } async fn pause_instance(&self, id: &str) -> Result<(), String> { - set_instance_context_str(Some(id)); + let label = { + let instances = self.instances.read().await; + resolve_instance_label(&instances, id) + }; + set_instance_context_str(Some(&label)); let faker = { let instances = self.instances.read().await; @@ -94,7 +117,11 @@ impl InstanceLifecycle for AppState { } async fn resume_instance(&self, id: &str) -> Result<(), String> { - set_instance_context_str(Some(id)); + let label = { + let instances = self.instances.read().await; + resolve_instance_label(&instances, id) + }; + set_instance_context_str(Some(&label)); let faker = { let instances = self.instances.read().await; @@ -113,7 +140,11 @@ impl InstanceLifecycle for AppState { } async fn update_instance(&self, id: &str) -> Result { - set_instance_context_str(Some(id)); + let label = { + let instances = self.instances.read().await; + resolve_instance_label(&instances, id) + }; + set_instance_context_str(Some(&label)); let faker = { let instances = self.instances.read().await; @@ -137,7 +168,11 @@ impl InstanceLifecycle for AppState { } async fn update_stats_only(&self, id: &str) -> Result { - set_instance_context_str(Some(id)); + let label = { + let instances = self.instances.read().await; + resolve_instance_label(&instances, id) + }; + set_instance_context_str(Some(&label)); let faker = { let instances = self.instances.read().await; diff --git a/rustatio-server/src/services/scheduler.rs b/rustatio-server/src/services/scheduler.rs index fa86544..067e939 100644 --- a/rustatio-server/src/services/scheduler.rs +++ b/rustatio-server/src/services/scheduler.rs @@ -102,7 +102,15 @@ async fn update_all_running_instances( continue; } - set_instance_context_str(Some(&id)); + let label = { + let guard = instances.read().await; + guard + .get(&id) + .map(|instance| instance.summary.name.clone()) + .filter(|name| !name.is_empty()) + .unwrap_or_else(|| id.clone()) + }; + set_instance_context_str(Some(&label)); if let Err(e) = faker.update().await { tracing::warn!("Scheduler: update failed for instance {}: {}", id, e); continue; diff --git a/rustatio-server/src/services/state.rs b/rustatio-server/src/services/state.rs index 9f7467f..8b47257 100644 --- a/rustatio-server/src/services/state.rs +++ b/rustatio-server/src/services/state.rs @@ -587,7 +587,7 @@ impl AppState { } async fn create_instance_internal(&self, context: InstanceBuildContext) -> Result<(), String> { - set_instance_context_str(Some(&context.id)); + set_instance_context_str(Some(context.summary.name.as_str())); let id = context.id.clone(); let existing = self.collect_existing_instance_state(&context).await; diff --git a/rustatio-wasm/src/lib.rs b/rustatio-wasm/src/lib.rs index a60a291..8fcf470 100644 --- a/rustatio-wasm/src/lib.rs +++ b/rustatio-wasm/src/lib.rs @@ -529,7 +529,7 @@ pub async fn grid_stop(ids_json: JsValue) -> Result { } #[wasm_bindgen] -pub async fn grid_pause(ids_json: JsValue) -> Result { +pub fn grid_pause(ids_json: JsValue) -> Result { let ids: Vec = serde_wasm_bindgen::from_value(ids_json).map_err(|e| JsValue::from_str(&e.to_string()))?; @@ -557,7 +557,7 @@ pub async fn grid_pause(ids_json: JsValue) -> Result { } #[wasm_bindgen] -pub async fn grid_resume(ids_json: JsValue) -> Result { +pub fn grid_resume(ids_json: JsValue) -> Result { let ids: Vec = serde_wasm_bindgen::from_value(ids_json).map_err(|e| JsValue::from_str(&e.to_string()))?; @@ -704,7 +704,7 @@ pub fn set_instance_tags(id: u32, tags_json: JsValue) -> Result<(), JsValue> { } #[wasm_bindgen] -pub async fn list_summaries() -> Result { +pub fn list_summaries() -> Result { let mut summaries: Vec = Vec::new(); // Collect IDs first to avoid borrow issues with async get_stats