use tracing::level_filters::LevelFilter;
use tracing_subscriber::filter::Targets;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;
pub const FEATURE_TARGETS: &[&str] = &["gc", "env", "ir", "jit"];
pub const NOISY_TARGETS: &[&str] = &[
"cranelift_codegen",
"cranelift_frontend",
"cranelift_jit",
"cranelift_module",
"cranelift_native",
"cranelift_object",
"regalloc2",
];
pub fn base_filter(default: impl Into<LevelFilter>) -> Targets {
let mut filter = Targets::new().with_default(default.into());
for target in NOISY_TARGETS {
filter = filter.with_target(*target, LevelFilter::WARN);
}
for target in FEATURE_TARGETS {
filter = filter.with_target(*target, LevelFilter::OFF);
}
filter
}
pub fn apply_x_flag(mut filter: Targets, spec: &str) -> Result<Targets, String> {
let (level_str, targets) = spec
.split_once(':')
.ok_or_else(|| format!("expected <level>:<targets>, got: {spec}"))?;
let level = match level_str {
"debug" => LevelFilter::DEBUG,
"trace" => LevelFilter::TRACE,
other => {
return Err(format!(
"unknown level '{other}', expected 'debug' or 'trace'"
));
}
};
for target in targets.split(',') {
let target = target.trim();
if target.is_empty() {
continue;
}
filter = filter.with_target(target.to_string(), level);
}
Ok(filter)
}
pub fn init(filter: Targets) {
let _ = tracing_subscriber::registry()
.with(filter)
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
.try_init();
}
pub fn apply_rust_log(base: Targets) -> Targets {
let Ok(spec) = std::env::var("RUST_LOG") else {
return base;
};
if spec.trim().is_empty() {
return base;
}
match spec.parse::<Targets>() {
Ok(filter) => filter,
Err(e) => {
eprintln!("cljrs: ignoring RUST_LOG ({e}); using the default log filter");
base
}
}
}
pub fn init_from_env() -> Result<(), String> {
let mut filter = apply_rust_log(Targets::new());
if let Ok(spec) = std::env::var("CLJRS_X_FLAG")
&& !spec.trim().is_empty()
{
filter = apply_x_flag(filter, &spec).map_err(|e| format!("invalid CLJRS_X_FLAG: {e}"))?;
}
init(filter);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::Level;
#[test]
fn x_flag_raises_only_the_named_targets() {
let filter = apply_x_flag(base_filter(Level::INFO), "debug:gc,jit").unwrap();
assert!(filter.would_enable("gc", &Level::DEBUG));
assert!(filter.would_enable("jit", &Level::DEBUG));
assert!(!filter.would_enable("gc", &Level::TRACE));
assert!(!filter.would_enable("env", &Level::DEBUG));
}
#[test]
fn trace_level_enables_debug_too() {
let filter = apply_x_flag(Targets::new(), "trace:env").unwrap();
assert!(filter.would_enable("env", &Level::TRACE));
assert!(filter.would_enable("env", &Level::DEBUG));
}
#[test]
fn base_filter_pins_feature_and_noisy_targets() {
let filter = base_filter(Level::DEBUG);
for target in FEATURE_TARGETS {
assert!(
!filter.would_enable(target, &Level::DEBUG),
"{target} must stay off under a blanket --debug"
);
}
assert!(!filter.would_enable("cranelift_codegen", &Level::INFO));
assert!(filter.would_enable("cranelift_codegen", &Level::WARN));
assert!(filter.would_enable("some_other_crate", &Level::DEBUG));
}
#[test]
fn malformed_x_flags_are_rejected() {
assert!(apply_x_flag(Targets::new(), "bogus").is_err());
assert!(apply_x_flag(Targets::new(), "warn:gc").is_err());
}
#[test]
fn rust_log_replaces_the_base_but_never_silences_it() {
let restore = std::env::var("RUST_LOG").ok();
unsafe { std::env::remove_var("RUST_LOG") };
assert!(
apply_rust_log(base_filter(Level::INFO)).would_enable("anything", &Level::INFO),
"unset RUST_LOG keeps the base filter"
);
unsafe { std::env::set_var("RUST_LOG", " ") };
assert!(
apply_rust_log(base_filter(Level::INFO)).would_enable("anything", &Level::INFO),
"blank RUST_LOG keeps the base filter"
);
unsafe { std::env::set_var("RUST_LOG", "gc=debug") };
let parsed = apply_rust_log(base_filter(Level::INFO));
assert!(
parsed.would_enable("gc", &Level::DEBUG),
"a valid RUST_LOG replaces the base, unpinning the feature targets"
);
unsafe { std::env::set_var("RUST_LOG", "=[not a filter]=") };
let fallback = apply_rust_log(base_filter(Level::INFO));
assert!(
fallback.would_enable("anything", &Level::INFO),
"an unparseable RUST_LOG must not silence the process"
);
assert!(
!fallback.would_enable("gc", &Level::DEBUG),
"...and must leave the base filter's pins intact"
);
match restore {
Some(v) => unsafe { std::env::set_var("RUST_LOG", v) },
None => unsafe { std::env::remove_var("RUST_LOG") },
}
}
}