From bf7c3f870f23bf0512a410d553f50ad74af17089 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sat, 28 Feb 2026 08:04:33 -0600 Subject: [PATCH] Add file-based logging to avoid TUI corruption from stderr output Errors were only visible in the truncated status bar notification. Add tracing-appender to write to data/sba-scout.log with env-filter support (RUST_LOG), and add tracing::error! calls alongside all notification-only error paths for full diagnostic visibility. Co-Authored-By: Claude Opus 4.6 --- rust/Cargo.lock | 47 +++++++++++++++++++++++++++++++++++ rust/Cargo.toml | 3 ++- rust/src/main.rs | 19 ++++++++++++++ rust/src/screens/dashboard.rs | 2 ++ rust/src/screens/gameday.rs | 3 +++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index a55c961..664d960 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -270,6 +270,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -1371,6 +1380,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2221,6 +2239,7 @@ dependencies = [ "tokio", "toml", "tracing", + "tracing-appender", "tracing-subscriber", ] @@ -2903,12 +2922,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde_core", "time-core", + "time-macros", ] [[package]] @@ -2917,6 +2938,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3112,6 +3143,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -3150,10 +3193,14 @@ 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", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 31be65c..fad580a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -31,7 +31,8 @@ thiserror = "2" # Logging tracing = "0.1" -tracing-subscriber = "0.3" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-appender = "0.2" # Date/time chrono = { version = "0.4", features = ["serde"] } diff --git a/rust/src/main.rs b/rust/src/main.rs index 9f9d189..7d3edd6 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -5,11 +5,27 @@ use ratatui::DefaultTerminal; use sqlx::sqlite::SqlitePool; use tokio::sync::mpsc; use tokio::time::{interval, Duration}; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_subscriber::EnvFilter; use sba_scout::app::{App, AppMessage}; use sba_scout::config; use sba_scout::db; +fn init_logging(log_dir: &std::path::Path) -> WorkerGuard { + std::fs::create_dir_all(log_dir).ok(); + let file_appender = tracing_appender::rolling::never(log_dir, "sba-scout.log"); + let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); + tracing_subscriber::fmt() + .with_writer(non_blocking) + .with_env_filter( + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), + ) + .with_ansi(false) + .init(); + guard +} + #[tokio::main] async fn main() -> Result<()> { let settings = match config::load_settings() { @@ -20,6 +36,9 @@ async fn main() -> Result<()> { } }; + let log_dir = settings.db_path.parent().unwrap_or(std::path::Path::new("data")); + let _log_guard = init_logging(log_dir); + if let Some(parent) = settings.db_path.parent() { std::fs::create_dir_all(parent)?; } diff --git a/rust/src/screens/dashboard.rs b/rust/src/screens/dashboard.rs index 1271ece..38bc10b 100644 --- a/rust/src/screens/dashboard.rs +++ b/rust/src/screens/dashboard.rs @@ -56,6 +56,7 @@ impl DashboardState { let _ = tx.send(AppMessage::RosterLoaded(roster)); } Err(e) => { + tracing::error!("Failed to load roster: {e}"); let _ = tx.send(AppMessage::Notify( format!("Failed to load roster: {e}"), NotifyLevel::Error, @@ -119,6 +120,7 @@ impl DashboardState { ); } Err(e) => { + tracing::error!("Sync failed: {e}"); self.sync_state = SyncState::Error; self.sync_message = format!("Sync failed: {e}"); } diff --git a/rust/src/screens/gameday.rs b/rust/src/screens/gameday.rs index 7bb02ba..68e1024 100644 --- a/rust/src/screens/gameday.rs +++ b/rust/src/screens/gameday.rs @@ -157,6 +157,7 @@ impl GamedayState { let _ = tx_c.send(AppMessage::TeamsLoaded(teams)); } Err(e) => { + tracing::error!("Failed to load teams: {e}"); let _ = tx_c.send(AppMessage::Notify( format!("Failed to load teams: {e}"), NotifyLevel::Error, @@ -620,6 +621,7 @@ impl GamedayState { let _ = tx.send(AppMessage::PitchersLoaded(pitchers)); } Err(e) => { + tracing::error!("Failed to load pitchers: {e}"); let _ = tx.send(AppMessage::Notify( format!("Failed to load pitchers: {e}"), NotifyLevel::Error, @@ -661,6 +663,7 @@ impl GamedayState { let _ = tx.send(AppMessage::MatchupsCalculated(results)); } Err(e) => { + tracing::error!("Matchup calculation failed: {e}"); let _ = tx.send(AppMessage::Notify( format!("Matchup calculation failed: {e}"), NotifyLevel::Error,