use std::io::Write as _;
struct Directive {
target: String,
level: log::LevelFilter,
}
struct StderrLogger {
directives: Vec<Directive>,
max: log::LevelFilter,
}
impl StderrLogger {
fn resolve(&self, target: &str) -> log::LevelFilter {
for d in &self.directives {
if d.target.is_empty() || target.starts_with(&d.target) {
return d.level;
}
}
log::LevelFilter::Off
}
}
impl log::Log for StderrLogger {
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
metadata.level() <= self.max && metadata.level() <= self.resolve(metadata.target())
}
fn log(&self, record: &log::Record<'_>) {
if !self.enabled(record.metadata()) {
return;
}
let _ = writeln!(
std::io::stderr(),
"[{} {}] {}",
record.level(),
record.target(),
record.args()
);
}
fn flush(&self) {
let _ = std::io::stderr().flush();
}
}
fn parse(spec: &str) -> Vec<Directive> {
let spec = spec.split('/').next().unwrap_or("");
let mut directives: Vec<Directive> = Vec::new();
for part in spec.split(',').map(str::trim).filter(|p| !p.is_empty()) {
let (target, level) = match part.split_once('=') {
Some((t, "")) if t.trim().is_empty() => continue,
Some((t, "")) => (t.trim(), log::LevelFilter::Trace),
Some((t, l)) => match l.trim().parse() {
Ok(level) => (t.trim(), level),
Err(_) => continue,
},
None => match part.parse() {
Ok(level) => ("", level),
Err(_) => (part, log::LevelFilter::Trace),
},
};
directives.push(Directive { target: target.to_owned(), level });
}
if directives.is_empty() {
directives.push(Directive { target: String::new(), level: log::LevelFilter::Error });
}
directives.sort_by(|a, b| b.target.len().cmp(&a.target.len()));
directives
}
pub fn try_init() -> Result<(), log::SetLoggerError> {
let directives = parse(&std::env::var("RUST_LOG").unwrap_or_default());
let max = directives.iter().map(|d| d.level).max().unwrap_or(log::LevelFilter::Error);
log::set_boxed_logger(Box::new(StderrLogger { directives, max }))?;
log::set_max_level(max);
Ok(())
}