Skip to main content

ig_client/utils/
logger.rs

1use std::env;
2use std::sync::Once;
3use tracing::Level;
4use tracing_subscriber::FmtSubscriber;
5
6static INIT: Once = Once::new();
7
8/// Maps a `LOGLEVEL`-style string to a [`tracing::Level`].
9///
10/// Matching is case-insensitive. Any value that is not one of `TRACE`, `DEBUG`,
11/// `WARN`, or `ERROR` (including empty or unknown strings) falls back to the
12/// default level, [`Level::INFO`].
13#[must_use]
14#[inline]
15pub(crate) fn level_from_str(s: &str) -> Level {
16    match s.trim().to_uppercase().as_str() {
17        "TRACE" => Level::TRACE,
18        "DEBUG" => Level::DEBUG,
19        "WARN" => Level::WARN,
20        "ERROR" => Level::ERROR,
21        // Unknown / empty / "INFO" all resolve to the default.
22        _ => Level::INFO,
23    }
24}
25
26/// Sets up the logger for the application.
27///
28/// The logger level is determined by the `LOGLEVEL` environment variable
29/// (case-insensitive). If the variable is not set or holds an unrecognized
30/// value, it defaults to `INFO`.
31///
32/// This installs a global subscriber and is intended for binaries, examples,
33/// and tests only — library code must not install a global subscriber.
34pub fn setup_logger() {
35    INIT.call_once(|| {
36        let level = level_from_str(&env::var("LOGLEVEL").unwrap_or_default());
37
38        let subscriber = FmtSubscriber::builder().with_max_level(level).finish();
39
40        // Ignore the error: a subscriber may already be installed by the host
41        // process (another binary, a test harness). `INIT` guards against this
42        // crate double-installing, and we never want setup to panic.
43        let _ = tracing::subscriber::set_global_default(subscriber);
44
45        tracing::debug!(%level, "log level configured");
46    });
47}
48
49#[cfg(test)]
50mod tests {
51    use super::level_from_str;
52    use tracing::Level;
53
54    #[test]
55    fn test_level_from_str_known_uppercase_variants() {
56        assert_eq!(level_from_str("TRACE"), Level::TRACE);
57        assert_eq!(level_from_str("DEBUG"), Level::DEBUG);
58        assert_eq!(level_from_str("INFO"), Level::INFO);
59        assert_eq!(level_from_str("WARN"), Level::WARN);
60        assert_eq!(level_from_str("ERROR"), Level::ERROR);
61    }
62
63    #[test]
64    fn test_level_from_str_is_case_insensitive() {
65        assert_eq!(level_from_str("debug"), Level::DEBUG);
66        assert_eq!(level_from_str("info"), Level::INFO);
67        assert_eq!(level_from_str("Warn"), Level::WARN);
68        assert_eq!(level_from_str("eRrOr"), Level::ERROR);
69    }
70
71    #[test]
72    fn test_level_from_str_trims_surrounding_whitespace() {
73        assert_eq!(level_from_str("  debug  "), Level::DEBUG);
74    }
75
76    #[test]
77    fn test_level_from_str_unknown_defaults_to_info() {
78        assert_eq!(level_from_str("INVALID"), Level::INFO);
79        assert_eq!(level_from_str(""), Level::INFO);
80        assert_eq!(level_from_str("warning"), Level::INFO);
81    }
82}