use std::sync::OnceLock;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::fmt::writer::BoxMakeWriter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer, Registry, reload};
type Installed = Box<dyn Layer<Registry> + Send + Sync>;
static RELOAD: OnceLock<reload::Handle<Installed, Registry>> = OnceLock::new();
#[derive(Debug)]
struct ResolvedFilter {
filter: EnvFilter,
from_env: bool,
}
fn build_env_filter(logging: &crate::config::LoggingConfig) -> Result<ResolvedFilter, String> {
if let Ok(filter) = EnvFilter::try_from_default_env() {
return Ok(ResolvedFilter {
filter,
from_env: true,
});
}
EnvFilter::try_new(&logging.filter)
.map(|filter| ResolvedFilter {
filter,
from_env: false,
})
.map_err(|error| {
format!(
"configuration error: logging.filter `{}` is not a valid tracing filter: {error}",
logging.filter
)
})
}
fn parse_target(target: &str) -> Result<BoxMakeWriter, String> {
match target {
"stdout" => Ok(BoxMakeWriter::new(std::io::stdout)),
"stderr" => Ok(BoxMakeWriter::new(std::io::stderr)),
other => Err(format!(
"configuration error: logging.target `{other}` is not a known target (stdout, stderr)"
)),
}
}
fn parse_span_events(span_events: &str) -> Result<FmtSpan, String> {
match span_events {
"none" => Ok(FmtSpan::NONE),
"close" => Ok(FmtSpan::CLOSE),
"full" => Ok(FmtSpan::FULL),
other => Err(format!(
"configuration error: logging.span_events `{other}` is not a known value (none, close, full)"
)),
}
}
fn ansi_enabled(configured: bool, no_color: Option<&str>) -> bool {
configured && !super::style::no_color_set(no_color)
}
pub(crate) struct PreparedLogging {
layer: Installed,
pub(crate) filter_from_env: bool,
}
pub(crate) fn prepare_logging(
logging: &crate::config::LoggingConfig,
) -> Result<PreparedLogging, String> {
let ResolvedFilter { filter, from_env } = build_env_filter(logging)?;
let writer = parse_target(&logging.target)?;
let span_events = parse_span_events(&logging.span_events)?;
let ansi = ansi_enabled(logging.ansi, std::env::var("NO_COLOR").ok().as_deref());
let layer: Installed = if logging.json_format {
tracing_subscriber::fmt::layer()
.json()
.flatten_event(logging.flatten_event)
.with_span_events(span_events)
.with_writer(writer)
.and_then(filter)
.boxed()
} else {
tracing_subscriber::fmt::layer()
.with_ansi(ansi)
.with_span_events(span_events)
.with_writer(writer)
.and_then(filter)
.boxed()
};
Ok(PreparedLogging {
layer,
filter_from_env: from_env,
})
}
pub(crate) fn publish_logging(prepared: PreparedLogging) -> bool {
RELOAD
.get()
.is_some_and(|handle| handle.reload(prepared.layer).is_ok())
}
pub fn init_logging(logging: &crate::config::LoggingConfig) -> Result<(), String> {
let prepared = prepare_logging(logging)?;
let (layer, handle) = reload::Layer::new(prepared.layer);
tracing_subscriber::registry().with(layer).init();
let _ = RELOAD.set(handle);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::level_filters::LevelFilter;
#[test]
fn a_malformed_logging_filter_is_reported_rather_than_panicking() {
let logging = crate::config::LoggingConfig {
filter: "this is not=a=valid=filter".to_string(),
..Default::default()
};
let error = build_env_filter(&logging).unwrap_err();
assert!(error.contains("logging.filter"), "{error}");
assert!(error.contains("this is not=a=valid=filter"), "{error}");
}
#[test]
fn a_valid_logging_filter_builds() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("RUST_LOG") };
let logging = crate::config::LoggingConfig {
filter: "acme_proxy=debug".to_string(),
..Default::default()
};
let resolved = build_env_filter(&logging).expect("a valid filter builds");
assert!(
!resolved.from_env,
"with RUST_LOG unset the filter comes from `logging.filter`",
);
}
#[test]
fn rust_log_wins_and_says_so() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::set_var("RUST_LOG", "acme_proxy=warn") };
let logging = crate::config::LoggingConfig {
filter: "acme_proxy=trace".to_string(),
..Default::default()
};
let resolved = build_env_filter(&logging).expect("RUST_LOG parses");
assert!(resolved.from_env);
unsafe { std::env::remove_var("RUST_LOG") };
}
#[test]
fn no_color_vetoes_ansi_and_an_empty_value_does_not() {
assert!(ansi_enabled(true, None));
assert!(!ansi_enabled(true, Some("1")));
assert!(!ansi_enabled(true, Some("anything")));
assert!(ansi_enabled(true, Some("")));
assert!(!ansi_enabled(false, None));
assert!(!ansi_enabled(false, Some("1")));
}
#[test]
fn both_logging_targets_resolve() {
assert!(parse_target("stdout").is_ok());
assert!(parse_target("stderr").is_ok());
}
#[test]
fn an_unknown_logging_target_is_reported() {
let error = parse_target("syslog").unwrap_err();
assert!(error.contains("logging.target"), "{error}");
assert!(error.contains("syslog"), "{error}");
assert!(error.contains("stdout"), "{error}");
}
#[test]
fn every_span_events_value_resolves() {
assert_eq!(parse_span_events("none").unwrap(), FmtSpan::NONE);
assert_eq!(parse_span_events("close").unwrap(), FmtSpan::CLOSE);
assert_eq!(parse_span_events("full").unwrap(), FmtSpan::FULL);
}
#[test]
fn an_unknown_span_events_value_is_reported() {
let error = parse_span_events("enter").unwrap_err();
assert!(error.contains("logging.span_events"), "{error}");
assert!(error.contains("enter"), "{error}");
assert!(error.contains("close"), "{error}");
}
#[test]
fn prepare_logging_reports_each_bad_key_by_name() {
for (logging, expected) in bad_key_cases() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("RUST_LOG") };
let Err(error) = prepare_logging(&logging) else {
panic!("`{expected}` must be refused, not built");
};
assert!(error.contains(expected), "{error}");
}
}
#[test]
fn publishing_without_an_installed_subscriber_is_a_no_op() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("RUST_LOG") };
let prepared =
prepare_logging(&crate::config::LoggingConfig::default()).expect("the defaults build");
assert!(!publish_logging(prepared));
}
#[test]
fn a_reloaded_filter_changes_what_is_enabled() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("RUST_LOG") };
let at_info = crate::config::LoggingConfig {
filter: "acme_proxy=info".to_string(),
target: "stderr".to_string(),
..Default::default()
};
init_logging(&at_info).expect("the subscriber installs");
assert_eq!(LevelFilter::current(), LevelFilter::INFO);
assert!(!tracing::enabled!(target: "acme_proxy", tracing::Level::DEBUG));
let at_debug = crate::config::LoggingConfig {
filter: "acme_proxy=debug".to_string(),
target: "stderr".to_string(),
..Default::default()
};
let prepared = prepare_logging(&at_debug).expect("the debug filter builds");
assert!(publish_logging(prepared), "the handle is installed");
assert_eq!(LevelFilter::current(), LevelFilter::DEBUG);
assert!(tracing::enabled!(target: "acme_proxy", tracing::Level::DEBUG));
}
#[test]
fn a_reloaded_format_swaps_the_whole_stack() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("RUST_LOG") };
init_logging(&crate::config::LoggingConfig {
target: "stderr".to_string(),
ansi: false,
..Default::default()
})
.expect("the subscriber installs");
let as_json = crate::config::LoggingConfig {
json_format: true,
flatten_event: true,
target: "stderr".to_string(),
span_events: "close".to_string(),
..Default::default()
};
let prepared = prepare_logging(&as_json).expect("the JSON stack builds");
assert!(publish_logging(prepared));
assert_eq!(
LevelFilter::current(),
LevelFilter::INFO,
"swapping the format must not lose the filter's level hint",
);
}
fn bad_key_cases() -> Vec<(crate::config::LoggingConfig, &'static str)> {
vec![
(
crate::config::LoggingConfig {
filter: "not=a=filter".to_string(),
..Default::default()
},
"logging.filter",
),
(
crate::config::LoggingConfig {
target: "nowhere".to_string(),
..Default::default()
},
"logging.target",
),
(
crate::config::LoggingConfig {
span_events: "sometimes".to_string(),
..Default::default()
},
"logging.span_events",
),
]
}
#[test]
fn init_logging_reports_each_bad_key_by_name() {
for (logging, expected) in bad_key_cases() {
let _guard = crate::config::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe { std::env::remove_var("RUST_LOG") };
let error = init_logging(&logging).unwrap_err();
assert!(error.contains(expected), "{error}");
}
}
#[test]
fn the_human_readable_subscriber_installs() {
let logging = crate::config::LoggingConfig {
target: "stderr".to_string(),
ansi: false,
span_events: "close".to_string(),
..Default::default()
};
assert!(init_logging(&logging).is_ok());
}
#[test]
fn the_json_subscriber_installs() {
let logging = crate::config::LoggingConfig {
json_format: true,
flatten_event: true,
span_events: "full".to_string(),
..Default::default()
};
assert!(init_logging(&logging).is_ok());
}
}