summaryrefslogtreecommitdiffstats
path: root/src/logging.rs
blob: 449612506ee0967f213b4fe40f215b3e700b9330 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//! 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::{Result, anyhow};
use tracing_subscriber::{EnvFilter, Registry, prelude::*, reload};

type FilterHandle = reload::Handle<EnvFilter, Registry>;

static FILTER: OnceLock<FilterHandle> = 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<String> {
    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<String> {
    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"))
}