Skip to main content

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}