1use 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}