#![forbid(unsafe_code)]
use std::sync::OnceLock;
use tracing_error::ErrorLayer;
use tracing_subscriber::reload;
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
static LOG_FILTER_RELOAD: OnceLock<reload::Handle<EnvFilter, Registry>> = OnceLock::new();
#[must_use]
pub fn build_env_filter(verbose: u8) -> EnvFilter {
match verbose {
0 => EnvFilter::new("error"),
1 => EnvFilter::new("warn,ssh_cli=info"),
2 => EnvFilter::new("warn,ssh_cli=debug"),
_ => EnvFilter::new("warn,ssh_cli=trace"),
}
}
pub fn bootstrap_logs() {
if LOG_FILTER_RELOAD.get().is_some() {
return;
}
let (filter_layer, handle) = reload::Layer::new(EnvFilter::new("error"));
let subscriber = Registry::default()
.with(filter_layer)
.with(ErrorLayer::default())
.with(
fmt::layer()
.with_writer(std::io::stderr)
.with_target(true)
.with_thread_names(true)
.with_ansi(false),
);
if tracing::subscriber::set_global_default(subscriber).is_ok() {
let _ = LOG_FILTER_RELOAD.set(handle);
let _ = tracing_log::LogTracer::builder()
.with_max_level(log::LevelFilter::Trace)
.init();
tracing::debug!("tracing subscriber installed (stderr, filter=error)");
}
}
pub fn initialize_logs(verbose: u8) {
let filter = build_env_filter(verbose);
if let Some(handle) = LOG_FILTER_RELOAD.get() {
match handle.reload(filter) {
Ok(()) => {
tracing::debug!(
verbose,
rust_log_set = std::env::var_os("RUST_LOG").is_some(),
"tracing filter reloaded"
);
}
Err(e) => {
tracing::warn!(err = %e, "failed to reload tracing filter");
}
}
return;
}
let _ = fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.with_target(true)
.with_thread_names(true)
.with_ansi(false)
.try_init();
let _ = tracing_log::LogTracer::builder()
.with_max_level(log::LevelFilter::Trace)
.init();
}
#[must_use]
pub fn has_reload_handle() -> bool {
LOG_FILTER_RELOAD.get().is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_env_filter_default_is_error() {
let prev = std::env::var_os("RUST_LOG");
crate::test_util::env::remove_var("RUST_LOG");
let f = build_env_filter(0);
assert_eq!(f.to_string(), "error");
match prev {
Some(v) => crate::test_util::env::set_var("RUST_LOG", v),
None => crate::test_util::env::remove_var("RUST_LOG"),
}
}
#[test]
fn build_env_filter_verbose_scopes_levels_to_this_crate() {
let prev = std::env::var_os("RUST_LOG");
crate::test_util::env::remove_var("RUST_LOG");
let f1 = build_env_filter(1).to_string();
let f2 = build_env_filter(2).to_string();
let f3 = build_env_filter(3).to_string();
assert!(
f1.contains("ssh_cli=info"),
"-v must enable info for the product crate, got {f1:?}"
);
assert!(
f2.contains("ssh_cli=debug"),
"-vv must enable debug for the product crate, got {f2:?}"
);
assert!(
f3.contains("ssh_cli=trace"),
"-vvv must enable trace for the product crate, got {f3:?}"
);
for f in [&f1, &f2, &f3] {
assert!(
!f.split(',')
.any(|d| d.trim() == "debug" || d.trim() == "trace" || d.trim() == "info"),
"verbose must NOT set a global level directive, got {f:?}"
);
}
match prev {
Some(v) => crate::test_util::env::set_var("RUST_LOG", v),
None => crate::test_util::env::remove_var("RUST_LOG"),
}
}
#[test]
fn bootstrap_logs_is_idempotent() {
bootstrap_logs();
bootstrap_logs();
let _ = has_reload_handle();
}
}