1use std::collections::HashMap;
20use std::sync::RwLock;
21
22static FEATURE_LEVELS: RwLock<Option<HashMap<String, Level>>> = RwLock::new(None);
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26pub enum Level {
27 Off = 0,
29 Debug = 1,
31 Trace = 2,
33}
34
35pub fn set_feature_level(feature: &str, level: Level) {
37 let mut guard = FEATURE_LEVELS.write().unwrap();
38 let map = guard.get_or_insert_with(HashMap::new);
39 map.insert(feature.to_string(), level);
40}
41
42pub fn feature_level(feature: &str) -> Level {
45 let guard = FEATURE_LEVELS.read().unwrap();
46 guard
47 .as_ref()
48 .and_then(|m| m.get(feature).copied())
49 .unwrap_or(Level::Off)
50}
51
52#[inline]
54pub fn is_enabled(feature: &str, level: Level) -> bool {
55 feature_level(feature) >= level
56}
57
58pub fn parse_x_flag(value: &str) -> Result<(), String> {
65 let (level_str, features_str) = value
66 .split_once(':')
67 .ok_or_else(|| format!("expected <level>:<features>, got: {value}"))?;
68
69 let level = match level_str {
70 "debug" => Level::Debug,
71 "trace" => Level::Trace,
72 other => {
73 return Err(format!(
74 "unknown level '{other}', expected 'debug' or 'trace'"
75 ));
76 }
77 };
78
79 for feature in features_str.split(',') {
80 let feature = feature.trim();
81 if feature.is_empty() {
82 continue;
83 }
84 set_feature_level(feature, level);
85 }
86 Ok(())
87}
88
89#[macro_export]
96macro_rules! feat_debug {
97 ($feature:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
98 if $crate::is_enabled($feature, $crate::Level::Debug) {
99 eprint!("[DEBUG:{}] ", $feature);
100 eprintln!($fmt $(, $arg)*);
101 }
102 };
103}
104
105#[macro_export]
111macro_rules! feat_trace {
112 ($feature:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
113 if $crate::is_enabled($feature, $crate::Level::Trace) {
114 eprint!("[TRACE:{}] ", $feature);
115 eprintln!($fmt $(, $arg)*);
116 }
117 };
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_parse_x_flag() {
126 parse_x_flag("debug:gc,jit").unwrap();
127 assert_eq!(feature_level("gc"), Level::Debug);
128 assert_eq!(feature_level("jit"), Level::Debug);
129 assert_eq!(feature_level("reader"), Level::Off);
130 }
131
132 #[test]
133 fn test_parse_x_flag_trace() {
134 parse_x_flag("trace:reader").unwrap();
135 assert_eq!(feature_level("reader"), Level::Trace);
136 assert!(is_enabled("reader", Level::Debug));
137 assert!(is_enabled("reader", Level::Trace));
138 }
139
140 #[test]
141 fn test_parse_x_flag_invalid() {
142 assert!(parse_x_flag("bogus").is_err());
143 assert!(parse_x_flag("warn:gc").is_err());
144 }
145}