diff options
| author | tv <tv@krebsco.de> | 2026-10-23 11:26:01 +0200 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-23 11:26:01 +0200 |
| commit | 64fbe99234425259d2758982caa52e1a62272d03 (patch) | |
| tree | ad75e3a0448987e43712ece8a32527cf3e9809cb /src | |
| parent | 3c53394def0ba28555fefa97f2a68e09b97a0b70 (diff) | |
allow changing log filtering while node runs
This also changes the default to tracing-subscriber.
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/log.rs | 27 | ||||
| -rw-r--r-- | src/api/mod.rs | 2 | ||||
| -rw-r--r-- | src/logging.rs | 57 | ||||
| -rw-r--r-- | src/main.rs | 3 |
4 files changed, 88 insertions, 1 deletions
diff --git a/src/api/log.rs b/src/api/log.rs new file mode 100644 index 0000000..4881b38 --- /dev/null +++ b/src/api/log.rs @@ -0,0 +1,27 @@ +//! /admin/log: read and replace the log filter of the running node. + +use axum::{body::Bytes, http::StatusCode}; +use tracing::{info, warn}; + +use crate::logging; + +/// `GET /admin/log`: the filter in effect, as plain text in `RUST_LOG` syntax. +pub(crate) async fn get_log_filter() -> Result<String, StatusCode> { + logging::filter().map_err(|error| { + warn!(error = %format!("{error:#}"), "Cannot read the log filter"); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +/// `PUT /admin/log`: replace the filter; the body is plain text in `RUST_LOG` syntax, +/// e.g. `info,continuum::swarm::sync=trace`. Answers with the filter now in effect; +/// 400 (with the reason) if the body doesn't parse, and the old filter stays. +pub(crate) async fn put_log_filter(body: Bytes) -> Result<String, (StatusCode, String)> { + let directives = std::str::from_utf8(&body) + .map_err(|_| (StatusCode::BAD_REQUEST, "The filter must be UTF-8\n".to_owned()))? + .trim(); + let applied = logging::set_filter(directives) + .map_err(|error| (StatusCode::BAD_REQUEST, format!("{error:#}\n")))?; + info!(filter = %applied, "Log filter replaced"); + Ok(applied + "\n") +} diff --git a/src/api/mod.rs b/src/api/mod.rs index ea3ad41..7328059 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod auth; mod debug; +mod log; mod peers; mod store; @@ -37,6 +38,7 @@ pub(crate) fn router(state: AppState, auth_state: AuthState, debug_api: bool) -> .route("/stream/{*key}", get(store::stream_store_entry)) .route("/peers", get(peers::list_peers)) .route("/peers/{peer}", delete(peers::delete_peer)) + .route("/admin/log", get(log::get_log_filter).put(log::put_log_filter)) .merge(crate::remake::router::<AppState>()); if debug_api { diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..c87837c --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,57 @@ +//! 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<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")) +} diff --git a/src/main.rs b/src/main.rs index 7151032..67cb366 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use url::Url; mod api; mod document; +mod logging; mod remake; mod state; mod swarm; @@ -97,7 +98,7 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { - console_subscriber::init(); + logging::init(); let args = Args::parse(); |
