1use chrono::Local;
2use owo_colors::{AnsiColors, OwoColorize};
3use std::{env, fmt::Display, sync::OnceLock};
4
5static LOG_CONFIG: OnceLock<LogConfig> = OnceLock::new();
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum Level {
10 Debug,
12 Info,
14 Warn,
16 Error,
18}
19
20impl Level {
21 pub fn as_str(&self) -> &'static str {
23 match self {
24 Level::Debug => "DEBUG",
25 Level::Info => "INFO",
26 Level::Warn => "WARN",
27 Level::Error => "ERROR",
28 }
29 }
30
31 pub fn from_str(s: &str) -> Option<Self> {
33 match s.to_uppercase().as_str() {
34 "DEBUG" => Some(Level::Debug),
35 "INFO" => Some(Level::Info),
36 "WARN" | "WARNING" => Some(Level::Warn),
37 "ERROR" | "ERR" => Some(Level::Error),
38 _ => None,
39 }
40 }
41
42 pub fn from_env() -> Self {
44 env::var("RUST_LOG")
45 .ok()
46 .and_then(|s| Self::from_str(&s))
47 .unwrap_or(Level::Info)
48 }
49}
50
51impl Display for Level {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "{}", self.as_str())
54 }
55}
56
57pub(crate) const DEFAULT_PROX_PREFIX: &str = "prox";
58
59#[derive(Debug, Clone)]
61pub struct LogConfig {
62 pub prox_prefix: String,
64 pub prefix_width: usize,
66 pub timestamp: bool,
68 pub level: Level,
70}
71
72impl Default for LogConfig {
73 fn default() -> Self {
74 Self {
75 prox_prefix: DEFAULT_PROX_PREFIX.to_string(),
76 prefix_width: 8,
77 timestamp: false,
78 level: Level::from_env(),
79 }
80 }
81}
82
83pub fn init_logging(config: LogConfig) {
85 LOG_CONFIG.set(config).ok();
86}
87
88fn should_log(level: Level) -> bool {
89 let config = LOG_CONFIG.get().cloned().unwrap_or_default();
90 level >= config.level
91}
92
93pub fn log_prox_internal(level: Level, message: &str) {
95 if !should_log(level) {
96 return;
97 }
98
99 let config = LOG_CONFIG.get().cloned().unwrap_or_default();
100
101 let color = match level {
102 Level::Debug => AnsiColors::BrightBlack,
103 Level::Info => AnsiColors::Green,
104 Level::Warn => AnsiColors::Yellow,
105 Level::Error => AnsiColors::Red,
106 };
107
108 let prefix = format_prefix(&config.prox_prefix, config.prefix_width, None);
109
110 let level_str = format!("[{level}]").color(color).to_string();
111
112 if config.timestamp {
113 let timestamp = Local::now().format("%H:%M:%S%.3f");
114 println!("{prefix} {level_str} [{timestamp}] {message}");
115 } else {
116 println!("{prefix} {level_str} {message}");
117 }
118}
119
120pub(crate) fn log_proc(proc_name: &str, message: &str, color: Option<AnsiColors>) {
122 let config = LOG_CONFIG.get().cloned().unwrap_or_default();
123 let prefix = format_prefix(proc_name, config.prefix_width, color);
124 println!("{prefix} {message}");
125}
126
127pub(crate) fn log_proc_error(proc_name: &str, message: &str, color: Option<AnsiColors>) {
128 let config = LOG_CONFIG.get().cloned().unwrap_or_default();
129 let prefix = format_prefix(proc_name, config.prefix_width, color);
130 eprintln!("{prefix} ERROR: {message}");
131}
132
133fn format_prefix(name: &str, width: usize, color: Option<AnsiColors>) -> String {
134 let formatted = format!("[{name:^0$}]", width);
135 match color {
136 Some(color) => formatted.color(color).to_string(),
137 None => formatted,
138 }
139}
140
141#[macro_export]
143macro_rules! debug {
144 ($($arg:tt)*) => {
145 $crate::logging::log_prox_internal($crate::logging::Level::Debug, &format!($($arg)*))
146 };
147}
148
149#[macro_export]
151macro_rules! info {
152 ($($arg:tt)*) => {
153 $crate::logging::log_prox_internal($crate::logging::Level::Info, &format!($($arg)*))
154 };
155}
156
157#[macro_export]
159macro_rules! warn {
160 ($($arg:tt)*) => {
161 $crate::logging::log_prox_internal($crate::logging::Level::Warn, &format!($($arg)*))
162 };
163}
164
165#[macro_export]
167macro_rules! error {
168 ($($arg:tt)*) => {
169 $crate::logging::log_prox_internal($crate::logging::Level::Error, &format!($($arg)*))
170 };
171}