use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum Verbosity {
Quiet = 0,
Normal = 1,
Verbose = 2,
Debug = 3,
Trace = 4,
}
pub const ENV_VAR: &str = "FLODL_VERBOSITY";
const UNINIT: u8 = 255;
static LEVEL: AtomicU8 = AtomicU8::new(UNINIT);
fn level_from_env() -> u8 {
match std::env::var(ENV_VAR) {
Ok(v) => match v.trim().to_ascii_lowercase().as_str() {
"0" | "quiet" => Verbosity::Quiet as u8,
"1" | "normal" => Verbosity::Normal as u8,
"2" | "verbose" => Verbosity::Verbose as u8,
"3" | "debug" => Verbosity::Debug as u8,
"4" | "trace" => Verbosity::Trace as u8,
_ => Verbosity::Normal as u8,
},
Err(_) => Verbosity::Normal as u8,
}
}
#[inline]
fn load_or_init() -> u8 {
let v = LEVEL.load(Ordering::Relaxed);
if v != UNINIT {
return v;
}
let from_env = level_from_env();
let _ = LEVEL.compare_exchange(UNINIT, from_env, Ordering::Relaxed, Ordering::Relaxed);
LEVEL.load(Ordering::Relaxed)
}
pub fn set_verbosity(level: Verbosity) {
LEVEL.store(level as u8, Ordering::Relaxed);
}
pub fn verbosity() -> Verbosity {
match load_or_init() {
0 => Verbosity::Quiet,
2 => Verbosity::Verbose,
3 => Verbosity::Debug,
4 => Verbosity::Trace,
_ => Verbosity::Normal,
}
}
#[inline]
pub fn enabled(level: Verbosity) -> bool {
load_or_init() >= level as u8
}
#[macro_export]
macro_rules! msg {
(@$level:expr, $($arg:tt)+) => {
if $crate::log::enabled($level) {
println!($($arg)+)
}
};
($($arg:tt)+) => {
if $crate::log::enabled($crate::log::Verbosity::Normal) {
println!($($arg)+)
}
};
}
#[macro_export]
macro_rules! verbose {
($($arg:tt)*) => {
if $crate::log::enabled($crate::log::Verbosity::Verbose) {
println!($($arg)*)
}
};
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
if $crate::log::enabled($crate::log::Verbosity::Debug) {
eprintln!($($arg)*)
}
};
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
if $crate::log::enabled($crate::log::Verbosity::Trace) {
eprintln!($($arg)*)
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verbosity_ordering() {
assert!(Verbosity::Quiet < Verbosity::Normal);
assert!(Verbosity::Normal < Verbosity::Verbose);
assert!(Verbosity::Verbose < Verbosity::Debug);
assert!(Verbosity::Debug < Verbosity::Trace);
}
#[test]
fn test_set_and_get() {
let orig = verbosity();
set_verbosity(Verbosity::Debug);
assert_eq!(verbosity(), Verbosity::Debug);
set_verbosity(Verbosity::Trace);
assert_eq!(verbosity(), Verbosity::Trace);
set_verbosity(Verbosity::Quiet);
assert_eq!(verbosity(), Verbosity::Quiet);
set_verbosity(Verbosity::Normal);
assert_eq!(verbosity(), Verbosity::Normal);
set_verbosity(orig);
}
#[test]
fn test_enabled() {
let orig = verbosity();
set_verbosity(Verbosity::Verbose);
assert!(enabled(Verbosity::Normal));
assert!(enabled(Verbosity::Verbose));
assert!(!enabled(Verbosity::Debug));
assert!(!enabled(Verbosity::Trace));
set_verbosity(Verbosity::Trace);
assert!(enabled(Verbosity::Normal));
assert!(enabled(Verbosity::Verbose));
assert!(enabled(Verbosity::Debug));
assert!(enabled(Verbosity::Trace));
set_verbosity(Verbosity::Quiet);
assert!(!enabled(Verbosity::Normal));
assert!(!enabled(Verbosity::Verbose));
set_verbosity(orig);
}
#[test]
fn test_msg_with_level() {
let orig = verbosity();
set_verbosity(Verbosity::Normal);
crate::msg!("normal message");
crate::msg!(@Verbosity::Verbose, "verbose message");
crate::msg!(@Verbosity::Debug, "debug: {}", 42);
set_verbosity(orig);
}
}