use std::cell::RefCell;
use std::sync::OnceLock;
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
}
static NODE_LABEL: OnceLock<String> = OnceLock::new();
thread_local! {
pub static THREAD_PREFIX: RefCell<String> = const { RefCell::new(String::new()) };
}
fn build_thread_prefix(host: &str, local_dev: u8, global_rank: Option<usize>) -> String {
match (host.is_empty(), global_rank) {
(true, None) => String::new(),
(true, Some(r)) => format!("[r{r}] "),
(false, None) => format!("[{host}:{local_dev}] "),
(false, Some(r)) => format!("[{host}:{local_dev}:r{r}] "),
}
}
pub fn set_node_label(host: impl Into<String>) {
let _ = NODE_LABEL.set(host.into());
}
pub fn set_thread_device(local_dev: u8, global_rank: Option<usize>) {
let host = NODE_LABEL.get().map(String::as_str).unwrap_or("");
let prefix = build_thread_prefix(host, local_dev, global_rank);
THREAD_PREFIX.with(|p| *p.borrow_mut() = prefix);
}
#[macro_export]
macro_rules! msg {
(@$level:expr, $($arg:tt)+) => {
if $crate::log::enabled($level) {
$crate::log::THREAD_PREFIX.with(|p|
println!("{}{}", p.borrow().as_str(), format_args!($($arg)+))
)
}
};
($($arg:tt)+) => {
if $crate::log::enabled($crate::log::Verbosity::Normal) {
$crate::log::THREAD_PREFIX.with(|p|
println!("{}{}", p.borrow().as_str(), format_args!($($arg)+))
)
}
};
}
#[macro_export]
macro_rules! verbose {
($($arg:tt)*) => {
if $crate::log::enabled($crate::log::Verbosity::Verbose) {
$crate::log::THREAD_PREFIX.with(|p|
println!("{}{}", p.borrow().as_str(), format_args!($($arg)*))
)
}
};
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
if $crate::log::enabled($crate::log::Verbosity::Debug) {
$crate::log::THREAD_PREFIX.with(|p|
eprintln!("{}{}", p.borrow().as_str(), format_args!($($arg)*))
)
}
};
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
if $crate::log::enabled($crate::log::Verbosity::Trace) {
$crate::log::THREAD_PREFIX.with(|p|
eprintln!("{}{}", p.borrow().as_str(), format_args!($($arg)*))
)
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_MUTEX: Mutex<()> = Mutex::new(());
#[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 _guard = TEST_MUTEX.lock().unwrap();
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 _guard = TEST_MUTEX.lock().unwrap();
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 _guard = TEST_MUTEX.lock().unwrap();
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);
}
#[test]
fn test_build_thread_prefix() {
assert_eq!(build_thread_prefix("", 0, None), "");
assert_eq!(build_thread_prefix("", 3, None), "");
assert_eq!(build_thread_prefix("", 0, Some(2)), "[r2] ");
assert_eq!(build_thread_prefix("node-a", 0, None), "[node-a:0] ");
assert_eq!(
build_thread_prefix("node-b", 1, Some(2)),
"[node-b:1:r2] "
);
assert_eq!(build_thread_prefix("node-a", 0, Some(0)), "[node-a:0:r0] ");
}
}