Skip to main content

faucet_cli/
obs.rs

1//! Shared observability setup (Prometheus + tracing) used by `run` and
2//! `schedule`. `install_observability` is idempotent, so calling this once per
3//! process is safe even though `main.rs` already installed a basic subscriber.
4
5use crate::config::PipelineConfig;
6use crate::error::CliResult;
7use faucet_core::{ObservabilityConfig, PrometheusConfig, TracingConfig, install_observability};
8
9/// Resolve the effective tracing level using the documented precedence:
10/// `FAUCET_LOG` > `RUST_LOG` > YAML `observability.tracing.level` > `None`.
11/// (`cli_flag` is reserved for a future call-site that forwards `--log-level`.)
12pub fn resolve_tracing_level(cli_flag: Option<&str>, yaml_level: Option<&str>) -> Option<String> {
13    if let Some(l) = cli_flag {
14        return Some(l.to_string());
15    }
16    if let Ok(l) = std::env::var("FAUCET_LOG")
17        && !l.is_empty()
18    {
19        return Some(l);
20    }
21    if let Ok(l) = std::env::var("RUST_LOG")
22        && !l.is_empty()
23    {
24        return Some(l);
25    }
26    yaml_level.map(|s| s.to_string())
27}
28
29/// Install Prometheus + tracing from the config's `observability:` block. Logs
30/// (does not fail) when a recorder/subscriber is already installed.
31pub fn install(cfg: &PipelineConfig) -> CliResult<()> {
32    let level = resolve_tracing_level(
33        None,
34        cfg.observability
35            .as_ref()
36            .and_then(|o| o.tracing.as_ref())
37            .and_then(|t| t.level.as_deref()),
38    );
39    let obs_cfg = ObservabilityConfig {
40        prometheus: cfg
41            .observability
42            .as_ref()
43            .and_then(|o| o.prometheus.as_ref())
44            .map(|p| PrometheusConfig {
45                listen: p.listen.clone(),
46                buckets: p.buckets.clone(),
47            }),
48        tracing: level.map(|l| TracingConfig { level: l }),
49    };
50    let report = install_observability(&obs_cfg)?;
51    if let Some(addr) = report.prometheus_listen.as_deref() {
52        tracing::info!("Prometheus /metrics listening on {addr}");
53    }
54    if report.prometheus_already_installed {
55        tracing::warn!(
56            "Prometheus recorder already installed; metrics route through the existing recorder"
57        );
58    }
59    if report.tracing_already_installed {
60        tracing::warn!(
61            "tracing subscriber already installed; logs route through the existing subscriber"
62        );
63    }
64    Ok(())
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use std::sync::Mutex;
71    static ENV_LOCK: Mutex<()> = Mutex::new(());
72
73    fn with_clean_env<F: FnOnce()>(f: F) {
74        let _g = ENV_LOCK.lock().unwrap();
75        unsafe {
76            std::env::remove_var("FAUCET_LOG");
77            std::env::remove_var("RUST_LOG");
78        }
79        f();
80    }
81
82    #[test]
83    fn cli_flag_beats_env_and_yaml() {
84        with_clean_env(|| {
85            unsafe {
86                std::env::set_var("FAUCET_LOG", "debug");
87                std::env::set_var("RUST_LOG", "trace");
88            }
89            assert_eq!(
90                resolve_tracing_level(Some("error"), Some("info")).as_deref(),
91                Some("error")
92            );
93        });
94    }
95
96    #[test]
97    fn faucet_log_beats_rust_log_and_yaml() {
98        with_clean_env(|| {
99            unsafe {
100                std::env::set_var("FAUCET_LOG", "debug");
101                std::env::set_var("RUST_LOG", "trace");
102            }
103            assert_eq!(
104                resolve_tracing_level(None, Some("info")).as_deref(),
105                Some("debug")
106            );
107        });
108    }
109
110    #[test]
111    fn rust_log_beats_yaml() {
112        with_clean_env(|| {
113            unsafe {
114                std::env::set_var("RUST_LOG", "trace");
115            }
116            assert_eq!(
117                resolve_tracing_level(None, Some("info")).as_deref(),
118                Some("trace")
119            );
120        });
121    }
122
123    #[test]
124    fn yaml_used_when_no_flag_or_env() {
125        with_clean_env(|| {
126            assert_eq!(
127                resolve_tracing_level(None, Some("info")).as_deref(),
128                Some("info")
129            );
130        });
131    }
132
133    #[test]
134    fn none_returned_when_nothing_set() {
135        with_clean_env(|| {
136            assert_eq!(resolve_tracing_level(None, None), None);
137        });
138    }
139}