use chrono::Local;
use owo_colors::{AnsiColors, OwoColorize};
use std::{env, fmt::Display, sync::OnceLock};
static LOG_CONFIG: OnceLock<LogConfig> = OnceLock::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Debug,
Info,
Warn,
Error,
}
impl Level {
pub fn as_str(&self) -> &'static str {
match self {
Level::Debug => "DEBUG",
Level::Info => "INFO",
Level::Warn => "WARN",
Level::Error => "ERROR",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"DEBUG" => Some(Level::Debug),
"INFO" => Some(Level::Info),
"WARN" | "WARNING" => Some(Level::Warn),
"ERROR" | "ERR" => Some(Level::Error),
_ => None,
}
}
pub fn from_env() -> Self {
env::var("RUST_LOG")
.ok()
.and_then(|s| Self::from_str(&s))
.unwrap_or(Level::Info)
}
}
impl Display for Level {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub(crate) const DEFAULT_PROX_PREFIX: &str = "prox";
#[derive(Debug, Clone)]
pub struct LogConfig {
pub prox_prefix: String,
pub prefix_width: usize,
pub timestamp: bool,
pub level: Level,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
prox_prefix: DEFAULT_PROX_PREFIX.to_string(),
prefix_width: 8,
timestamp: false,
level: Level::from_env(),
}
}
}
pub fn init_logging(config: LogConfig) {
LOG_CONFIG.set(config).ok();
}
fn should_log(level: Level) -> bool {
let config = LOG_CONFIG.get().cloned().unwrap_or_default();
level >= config.level
}
pub fn log_prox_internal(level: Level, message: &str) {
if !should_log(level) {
return;
}
let config = LOG_CONFIG.get().cloned().unwrap_or_default();
let color = match level {
Level::Debug => AnsiColors::BrightBlack,
Level::Info => AnsiColors::Green,
Level::Warn => AnsiColors::Yellow,
Level::Error => AnsiColors::Red,
};
let prefix = format_prefix(&config.prox_prefix, config.prefix_width, None);
let level_str = format!("[{level}]").color(color).to_string();
if config.timestamp {
let timestamp = Local::now().format("%H:%M:%S%.3f");
println!("{prefix} {level_str} [{timestamp}] {message}");
} else {
println!("{prefix} {level_str} {message}");
}
}
pub(crate) fn log_proc(proc_name: &str, message: &str, color: Option<AnsiColors>) {
let config = LOG_CONFIG.get().cloned().unwrap_or_default();
let prefix = format_prefix(proc_name, config.prefix_width, color);
println!("{prefix} {message}");
}
pub(crate) fn log_proc_error(proc_name: &str, message: &str, color: Option<AnsiColors>) {
let config = LOG_CONFIG.get().cloned().unwrap_or_default();
let prefix = format_prefix(proc_name, config.prefix_width, color);
eprintln!("{prefix} ERROR: {message}");
}
fn format_prefix(name: &str, width: usize, color: Option<AnsiColors>) -> String {
let formatted = format!("[{name:^0$}]", width);
match color {
Some(color) => formatted.color(color).to_string(),
None => formatted,
}
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
$crate::logging::log_prox_internal($crate::logging::Level::Debug, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
$crate::logging::log_prox_internal($crate::logging::Level::Info, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
$crate::logging::log_prox_internal($crate::logging::Level::Warn, &format!($($arg)*))
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
$crate::logging::log_prox_internal($crate::logging::Level::Error, &format!($($arg)*))
};
}