Skip to main content

loonfs_server/
trace.rs

1//! Tracing subscriber setup from environment variables.
2
3use std::env;
4
5use thiserror::Error;
6use tracing_subscriber::fmt::format::FmtSpan;
7use tracing_subscriber::EnvFilter;
8
9const TRACE_ENV: &str = "LOONFS_TRACE";
10const RUST_LOG_ENV: &str = "RUST_LOG";
11const DEFAULT_TRACE_FILTER: &str =
12    "loonfs_server=info,loonfs_grep=info,loonfs=info,loonfs_core=info";
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15struct TraceConfig {
16    filter: String,
17}
18
19#[derive(Debug, Error)]
20pub enum TraceInitError {
21    #[error("unsupported LOONFS_TRACE value `{0}`; expected `json`")]
22    UnsupportedMode(String),
23    #[error("invalid RUST_LOG filter: {0}")]
24    Filter(String),
25    #[error("failed to initialize tracing subscriber: {0}")]
26    Subscriber(String),
27}
28
29pub fn init_tracing_from_env() -> Result<(), TraceInitError> {
30    let Some(config) =
31        trace_config_from_env(env::var(TRACE_ENV).ok(), env::var(RUST_LOG_ENV).ok())?
32    else {
33        return Ok(());
34    };
35    let filter = config
36        .filter
37        .parse::<EnvFilter>()
38        .map_err(|err| TraceInitError::Filter(err.to_string()))?;
39    tracing_subscriber::fmt()
40        .json()
41        .with_env_filter(filter)
42        .with_span_events(FmtSpan::CLOSE)
43        .try_init()
44        .map_err(|err| TraceInitError::Subscriber(err.to_string()))
45}
46
47fn trace_config_from_env(
48    trace_env: Option<String>,
49    rust_log_env: Option<String>,
50) -> Result<Option<TraceConfig>, TraceInitError> {
51    let Some(trace_mode) = trace_env else {
52        return Ok(None);
53    };
54    if trace_mode.trim().is_empty() {
55        return Ok(None);
56    }
57    if trace_mode != "json" {
58        return Err(TraceInitError::UnsupportedMode(trace_mode));
59    }
60    Ok(Some(TraceConfig {
61        filter: rust_log_env
62            .filter(|value| !value.trim().is_empty())
63            .unwrap_or_else(|| DEFAULT_TRACE_FILTER.to_owned()),
64    }))
65}
66
67#[cfg(test)]
68mod tests {
69    use super::{trace_config_from_env, DEFAULT_TRACE_FILTER};
70
71    #[test]
72    fn tracing_is_disabled_without_env() {
73        assert!(trace_config_from_env(None, None)
74            .expect("trace config parses")
75            .is_none());
76        assert!(trace_config_from_env(Some(String::new()), None)
77            .expect("blank trace config parses")
78            .is_none());
79    }
80
81    #[test]
82    fn json_tracing_uses_default_filter_when_rust_log_missing() {
83        let config = trace_config_from_env(Some("json".to_owned()), None)
84            .expect("trace config parses")
85            .expect("enabled tracing config");
86        assert_eq!(config.filter, DEFAULT_TRACE_FILTER);
87    }
88
89    #[test]
90    fn json_tracing_uses_rust_log_when_present() {
91        let config = trace_config_from_env(
92            Some("json".to_owned()),
93            Some("loonfs_core=debug".to_owned()),
94        )
95        .expect("trace config parses")
96        .expect("enabled tracing config");
97        assert_eq!(config.filter, "loonfs_core=debug");
98    }
99
100    #[test]
101    fn unsupported_trace_mode_is_rejected() {
102        assert!(trace_config_from_env(Some("text".to_owned()), None).is_err());
103    }
104}