Skip to main content

reth_tracing/
log_handle.rs

1//! Global log handle for runtime filter changes.
2//!
3//! Provides a single global [`LogFilterHandle`] that collects reload handles from all
4//! reloadable layers (stdout, file, etc.). `set_log_verbosity` and `set_log_vmodule`
5//! update every registered layer in one shot.
6
7use tracing::level_filters::LevelFilter;
8use tracing_subscriber::{reload, EnvFilter, Registry};
9
10/// Type alias for a single layer's reload handle.
11pub type LogFilterReloadHandle = reload::Handle<EnvFilter, Registry>;
12
13/// Collects reload handles so all layers can be updated together.
14#[derive(Debug)]
15pub struct LogFilterHandle {
16    handles: Vec<LogFilterReloadHandle>,
17}
18
19impl LogFilterHandle {
20    /// Creates a new, empty handle collection.
21    const fn new() -> Self {
22        Self { handles: Vec::new() }
23    }
24
25    /// Adds a reload handle for a layer.
26    fn push(&mut self, handle: LogFilterReloadHandle) {
27        self.handles.push(handle);
28    }
29
30    /// Returns `true` if at least one handle is registered.
31    const fn is_available(&self) -> bool {
32        !self.handles.is_empty()
33    }
34
35    /// Reloads every registered layer with a fresh filter built by `make_filter`.
36    fn reload_all(
37        &self,
38        make_filter: impl Fn() -> Result<EnvFilter, String>,
39    ) -> Result<(), String> {
40        for handle in &self.handles {
41            let filter = make_filter()?;
42            handle.reload(filter).map_err(|e| e.to_string())?;
43        }
44        Ok(())
45    }
46}
47
48/// Single global log handle shared by all reloadable layers.
49static LOG_HANDLE: std::sync::Mutex<LogFilterHandle> =
50    std::sync::Mutex::new(LogFilterHandle::new());
51
52/// Registers a reload handle for a layer (stdout, file, etc.).
53///
54/// Can be called multiple times — each handle is appended.
55pub fn install_log_handle(handle: LogFilterReloadHandle) {
56    LOG_HANDLE.lock().expect("log handle poisoned").push(handle);
57}
58
59/// Returns `true` if at least one global log handle is available.
60pub fn log_handle_available() -> bool {
61    LOG_HANDLE.lock().expect("log handle poisoned").is_available()
62}
63
64/// Sets the global log verbosity level.
65///
66/// - 0: OFF
67/// - 1: ERROR
68/// - 2: WARN
69/// - 3: INFO
70/// - 4: DEBUG
71/// - 5+: TRACE
72///
73/// Updates all reloadable layers (stdout, file, etc.).
74///
75/// Returns an error if no log handle is installed or if the reload fails.
76pub fn set_log_verbosity(level: usize) -> Result<(), String> {
77    let guard = LOG_HANDLE.lock().expect("log handle poisoned");
78
79    if !guard.is_available() {
80        return Err("Log filter reload not available".to_string());
81    }
82
83    let level_filter = match level {
84        0 => LevelFilter::OFF,
85        1 => LevelFilter::ERROR,
86        2 => LevelFilter::WARN,
87        3 => LevelFilter::INFO,
88        4 => LevelFilter::DEBUG,
89        _ => LevelFilter::TRACE,
90    };
91
92    guard.reload_all(|| {
93        Ok(EnvFilter::builder().with_default_directive(level_filter.into()).parse_lossy(""))
94    })
95}
96
97/// Sets module-specific log levels using a pattern string.
98///
99/// Pattern format follows the `RUST_LOG` environment variable syntax:
100/// - `module1=level1,module2=level2`
101/// - Example: `reth::sync=debug,reth::net=trace`
102/// - Example: `info,reth::stages=debug`
103///
104/// An empty string resets the filter to the default level (INFO).
105///
106/// Updates all reloadable layers (stdout, file, etc.).
107///
108/// Returns an error if no log handle is installed or if parsing fails.
109pub fn set_log_vmodule(pattern: &str) -> Result<(), String> {
110    let guard = LOG_HANDLE.lock().expect("log handle poisoned");
111
112    if !guard.is_available() {
113        return Err("Log filter reload not available".to_string());
114    }
115
116    if pattern.trim().is_empty() {
117        guard.reload_all(|| {
118            Ok(EnvFilter::builder()
119                .with_default_directive(LevelFilter::INFO.into())
120                .parse_lossy(""))
121        })
122    } else {
123        // Validate the pattern once before reloading all handles
124        EnvFilter::try_new(pattern).map_err(|e| format!("Invalid filter pattern: {e}"))?;
125        guard.reload_all(|| {
126            EnvFilter::try_new(pattern).map_err(|e| format!("Invalid filter pattern: {e}"))
127        })
128    }
129}