//! Logging: a compact fmt logger whose filter can be replaced while the node runs //! (`/admin/log`), and, with the `tokio-console` feature, a tokio-console layer. //! //! The filter starts from `RUST_LOG`, or `info` without it. It applies to the fmt //! logger only, so the console layer keeps getting the runtime events it needs. use std::sync::OnceLock; use anyhow::{anyhow, Result}; use tracing_subscriber::{prelude::*, reload, EnvFilter, Registry}; type FilterHandle = reload::Handle; static FILTER: OnceLock = OnceLock::new(); /// Installs the global subscriber. Call once, first thing in main. pub(crate) fn init() { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let (filter, handle) = reload::Layer::new(filter); let registry = tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().compact().with_filter(filter)); #[cfg(feature = "tokio-console")] let registry = registry.with( console_subscriber::spawn() .with_filter(EnvFilter::new("tokio=trace,runtime=trace")), ); registry.init(); let _ = FILTER.set(handle); } /// The filter in effect, in `RUST_LOG` syntax. pub(crate) fn filter() -> Result { handle()? .with_current(|filter| filter.to_string()) .map_err(|error| anyhow!("Reading the log filter: {error}")) } /// Replaces the filter. `directives` uses `RUST_LOG` syntax, e.g. /// `info,continuum::swarm::sync=trace`. An invalid one leaves the old filter in place. pub(crate) fn set_filter(directives: &str) -> Result { let filter = EnvFilter::try_new(directives) .map_err(|error| anyhow!("Invalid log filter {directives:?}: {error}"))?; let applied = filter.to_string(); handle()? .reload(filter) .map_err(|error| anyhow!("Replacing the log filter: {error}"))?; Ok(applied) } fn handle() -> Result<&'static FilterHandle> { FILTER .get() .ok_or_else(|| anyhow!("Logging is not initialised")) }