sonos_cli/logging.rs
1//! Logging setup using `tracing` + `tracing-subscriber`.
2//!
3//! Initializes a global tracing subscriber with two layers:
4//! - **File layer** (always): writes to `~/.local/share/sonos/sonos.log`, truncated per session.
5//! - **Stderr layer** (CLI only): writes to stderr. Disabled in TUI mode to avoid corrupting the display.
6
7use std::fs::{self, File};
8use std::sync::Mutex;
9
10use tracing_subscriber::{fmt, prelude::*, EnvFilter};
11
12/// Initialize the global tracing subscriber.
13///
14/// Must be called early in `main()`, before any SDK calls (which emit tracing events).
15///
16/// - `verbosity`: from the `-v` flag count. 0 = warn (or RUST_LOG fallback), 1 = info, 2 = debug, 3+ = trace.
17/// - `is_tui`: when true, omits the stderr layer (ratatui owns the terminal).
18pub fn init_logging(verbosity: u8, is_tui: bool) {
19 let filter = build_filter(verbosity);
20
21 let file_layer =
22 create_log_file().map(|file| fmt::layer().with_writer(Mutex::new(file)).with_ansi(false));
23
24 let stderr_layer = if !is_tui {
25 Some(fmt::layer().with_writer(std::io::stderr).without_time())
26 } else {
27 None
28 };
29
30 tracing_subscriber::registry()
31 .with(filter)
32 .with(file_layer)
33 .with(stderr_layer)
34 .init();
35}
36
37/// Build an `EnvFilter` from the verbosity count.
38///
39/// When verbosity is 0, falls back to `RUST_LOG` env var if set, otherwise defaults to `warn`.
40/// When verbosity > 0, the `-v` flag takes precedence over `RUST_LOG`.
41fn build_filter(verbosity: u8) -> EnvFilter {
42 if verbosity == 0 {
43 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"))
44 } else {
45 let level = match verbosity {
46 1 => "info",
47 2 => "debug",
48 _ => "trace",
49 };
50 EnvFilter::new(level)
51 }
52}
53
54/// Create (or truncate) the log file, ensuring the parent directory exists.
55///
56/// Returns `None` with a warning on stderr if the file cannot be created.
57fn create_log_file() -> Option<File> {
58 let data_dir = dirs::data_local_dir()?;
59 let log_dir = data_dir.join("sonos");
60
61 if let Err(e) = fs::create_dir_all(&log_dir) {
62 eprintln!(
63 "warning: could not create log directory {}: {e}",
64 log_dir.display()
65 );
66 return None;
67 }
68
69 let log_path = log_dir.join("sonos.log");
70 match File::create(&log_path) {
71 Ok(file) => Some(file),
72 Err(e) => {
73 eprintln!(
74 "warning: could not create log file {}: {e}",
75 log_path.display()
76 );
77 None
78 }
79 }
80}