use crate::format::{Color, Style, Stylize}; use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub use crate::{__dlog_internal, debug, error, info, trace, warn};
macro_rules! define_levels {
($($level:ident => $value:expr, $color:expr),+ $(,)?) => {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Level {
Trace = 5, Debug = 4,
Info = 3,
Warn = 2,
Error = 1,
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
$(Level::$level => write!(f, stringify!($level))),+
}
}
}
impl Level {
fn color(&self) -> Color {
match self {
$(Level::$level => $color),+
}
}
}
};
}
define_levels! {
Trace => 5, Color::new(218, 0, 192), Debug => 4, Color::new( 96, 216, 216), Info => 3, Color::new( 24, 216, 16), Warn => 2, Color::new(232, 232, 64), Error => 1, Color::new(232, 72, 96), }
static MAX_LOG_LEVEL: AtomicUsize = AtomicUsize::new(Level::Info as usize);
pub fn set_max_level(level: Level) {
MAX_LOG_LEVEL.store(level as usize, Ordering::SeqCst);
}
pub fn enabled(level: Level) -> bool {
level as usize <= MAX_LOG_LEVEL.load(Ordering::Relaxed)
}
fn strip_ansi_escapes(src_str: &str) -> String {
let mut result = String::with_capacity(src_str.len());
let mut in_escape = false;
for c in src_str.chars() {
match c {
'\x1B' => in_escape = true,
'm' if in_escape => in_escape = false,
_ if !in_escape => result.push(c),
_ => (), }
}
result
}
const LEVEL_WIDTH: usize = 5;
pub trait DlogStyle {
fn format_log(&self, level: &Level, args: fmt::Arguments) -> String;
fn level_color(&self, level: &Level, msg: &str) -> String {
msg.color(level.color()).style(Style::Bold)
}
}
pub struct DefaultDlogStyle;
impl DlogStyle for DefaultDlogStyle {
fn format_log(&self, level: &Level, args: fmt::Arguments) -> String {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
let secs = now.as_secs();
let ms = now.subsec_millis();
let (hr, min, sec) = ((secs / 3600) % 24, (secs / 60) % 60, secs % 60);
let timestamp_str = format!("[{hr:02}:{min:02}:{sec:02}.{ms:03}]");
let styled_timestamp = timestamp_str.style(Style::Dim);
let level_display_str = level.to_string();
let padded_level_str = format!("{level_display_str:>LEVEL_WIDTH$}"); let styled_level_indicator = self.level_color(level, &padded_level_str);
let first_line_prefix_styled = format!("{} {} ", styled_timestamp, styled_level_indicator);
let content_start_column = strip_ansi_escapes(&first_line_prefix_styled).len();
let user_message_str = args.to_string();
let user_message_lines: Vec<&str> = user_message_str.lines().collect();
let mut output = String::new();
if user_message_lines.is_empty() {
output.push_str(&first_line_prefix_styled);
} else {
output.push_str(&first_line_prefix_styled);
output.push_str(user_message_lines[0]);
for (i, line_content) in user_message_lines.iter().enumerate().skip(1) {
output.push('\n');
output.push_str(&" ".repeat(content_start_column.saturating_sub(2)));
let continuation_char = if i == user_message_lines.len() - 1 {
"└ " } else {
"│ " };
output.push_str(&self.level_color(level, continuation_char));
output.push_str(line_content);
}
}
output.push_str("\x1b[0m");
output
}
fn level_color(&self, level: &Level, msg: &str) -> String {
msg.color(level.color()).style(Style::Bold)
}
}
pub fn log(style: &impl DlogStyle, level: Level, args: fmt::Arguments) {
if enabled(level) {
let log_message = style.format_log(&level, args);
println!("{}", log_message);
}
}
#[macro_export]
#[doc(hidden)] macro_rules! __dlog_internal {
($level:expr, $($arg:tt)+) => {
$crate::dlog::log(&$crate::dlog::DefaultDlogStyle, $level, format_args!($($arg)+))
};
}
#[macro_export]
macro_rules! error { ($($arg:tt)+) => { $crate::__dlog_internal!($crate::dlog::Level::Error, $($arg)+) }; }
#[macro_export]
macro_rules! warn { ($($arg:tt)+) => { $crate::__dlog_internal!($crate::dlog::Level::Warn, $($arg)+) }; }
#[macro_export]
macro_rules! info { ($($arg:tt)+) => { $crate::__dlog_internal!($crate::dlog::Level::Info, $($arg)+) }; }
#[macro_export]
macro_rules! debug { ($($arg:tt)+) => { $crate::__dlog_internal!($crate::dlog::Level::Debug, $($arg)+) }; }
#[macro_export]
macro_rules! trace { ($($arg:tt)+) => { $crate::__dlog_internal!($crate::dlog::Level::Trace, $($arg)+) }; }