prox/
logging.rs

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/// Levels for log filtering
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum Level {
10    /// Show logs at the Debug level and higher
11    Debug,
12    /// Show logs at the Info level and higher
13    Info,
14    /// Show logs at the Warn level and higher
15    Warn,
16    /// Show logs only at the Error level
17    Error,
18}
19
20impl Level {
21    /// Get the string representation of the log level
22    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    /// Create a log level from a string (case insensitive)
32    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    /// Get the log level from the RUST_LOG environment variable, defaulting to Info
43    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/// Configuration for logging
60#[derive(Debug, Clone)]
61pub struct LogConfig {
62    /// Prefix to use for prox internal logs
63    pub prox_prefix: String,
64    /// Minimum width of the process prefix in log output
65    pub prefix_width: usize,
66    /// Whether to show timestamps in prox logs (not process logs)
67    pub timestamp: bool,
68    /// Minimum log level to show
69    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
83/// Initialize logging with the given configuration
84pub 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
93/// Logging function that gets called by the logging macros and prints the log message
94pub 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
120// Direct functions for process logs (no macros needed for these)
121pub(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/// Log to the prox logger at debug level
142#[macro_export]
143macro_rules! debug {
144    ($($arg:tt)*) => {
145        $crate::logging::log_prox_internal($crate::logging::Level::Debug, &format!($($arg)*))
146    };
147}
148
149/// Log to the prox logger at info level
150#[macro_export]
151macro_rules! info {
152    ($($arg:tt)*) => {
153        $crate::logging::log_prox_internal($crate::logging::Level::Info, &format!($($arg)*))
154    };
155}
156
157/// Log to the prox logger at warn level
158#[macro_export]
159macro_rules! warn {
160    ($($arg:tt)*) => {
161        $crate::logging::log_prox_internal($crate::logging::Level::Warn, &format!($($arg)*))
162    };
163}
164
165/// Log to the prox logger at error level
166#[macro_export]
167macro_rules! error {
168    ($($arg:tt)*) => {
169        $crate::logging::log_prox_internal($crate::logging::Level::Error, &format!($($arg)*))
170    };
171}