use std::collections::HashMap;
use std::sync::RwLock;
static FEATURE_LEVELS: RwLock<Option<HashMap<String, Level>>> = RwLock::new(None);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Off = 0,
Debug = 1,
Trace = 2,
}
pub fn set_feature_level(feature: &str, level: Level) {
let mut guard = FEATURE_LEVELS.write().unwrap();
let map = guard.get_or_insert_with(HashMap::new);
map.insert(feature.to_string(), level);
}
pub fn feature_level(feature: &str) -> Level {
let guard = FEATURE_LEVELS.read().unwrap();
guard
.as_ref()
.and_then(|m| m.get(feature).copied())
.unwrap_or(Level::Off)
}
#[inline]
pub fn is_enabled(feature: &str, level: Level) -> bool {
feature_level(feature) >= level
}
pub fn parse_x_flag(value: &str) -> Result<(), String> {
let (level_str, features_str) = value
.split_once(':')
.ok_or_else(|| format!("expected <level>:<features>, got: {value}"))?;
let level = match level_str {
"debug" => Level::Debug,
"trace" => Level::Trace,
other => {
return Err(format!(
"unknown level '{other}', expected 'debug' or 'trace'"
));
}
};
for feature in features_str.split(',') {
let feature = feature.trim();
if feature.is_empty() {
continue;
}
set_feature_level(feature, level);
}
Ok(())
}
#[macro_export]
macro_rules! feat_debug {
($feature:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
if $crate::is_enabled($feature, $crate::Level::Debug) {
eprint!("[DEBUG:{}] ", $feature);
eprintln!($fmt $(, $arg)*);
}
};
}
#[macro_export]
macro_rules! feat_trace {
($feature:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
if $crate::is_enabled($feature, $crate::Level::Trace) {
eprint!("[TRACE:{}] ", $feature);
eprintln!($fmt $(, $arg)*);
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_x_flag() {
parse_x_flag("debug:gc,jit").unwrap();
assert_eq!(feature_level("gc"), Level::Debug);
assert_eq!(feature_level("jit"), Level::Debug);
assert_eq!(feature_level("reader"), Level::Off);
}
#[test]
fn test_parse_x_flag_trace() {
parse_x_flag("trace:reader").unwrap();
assert_eq!(feature_level("reader"), Level::Trace);
assert!(is_enabled("reader", Level::Debug));
assert!(is_enabled("reader", Level::Trace));
}
#[test]
fn test_parse_x_flag_invalid() {
assert!(parse_x_flag("bogus").is_err());
assert!(parse_x_flag("warn:gc").is_err());
}
}