use log::{Log, Metadata, Record, Level, SetLoggerError};
use colored::*; use chrono::Local; use std::fs::{File, OpenOptions};
use std::io::Write;
use std::sync::{Arc, Mutex};
pub use log::{error, warn, info, debug, trace, LevelFilter};
pub use colored::Color;
pub enum ArgColor {
Fixed(Color), LogLevel, None, }
pub struct ColorConfig {
pub error_color: Color,
pub warn_color: Color,
pub info_color: Color,
pub debug_color: Color,
pub trace_color: Color,
pub arg_color: ArgColor,
pub target_color: Color, pub datetime_color: Option<Color>,
}
impl Default for ColorConfig {
fn default() -> Self {
ColorConfig {
error_color: Color::Red,
warn_color: Color::Yellow,
info_color: Color::Green,
debug_color: Color::Blue,
trace_color: Color::Magenta,
arg_color: ArgColor::None,
target_color: Color::White,
datetime_color: Some(Color::BrightWhite), }
}
}
pub struct ChromaLog {
level: LevelFilter, color_config: ColorConfig, log_file: Option<Arc<Mutex<File>>>, }
impl ChromaLog {
pub fn init(level: LevelFilter, color_config: ColorConfig, log_file_path: Option<&str>) -> Result<(), SetLoggerError> {
let log_file = log_file_path.map(|path| {
Arc::new(Mutex::new(
OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(path)
.expect("Failed to open log file"),
))
});
let logger = ChromaLog { level, color_config, log_file };
log::set_boxed_logger(Box::new(logger))?;
log::set_max_level(level);
Ok(())
}
}
impl Log for ChromaLog {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let level = record.level();
let target = record.target();
let args = record.args();
let now = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
let colored_now = if let Some(color) = self.color_config.datetime_color {
now.to_string().color(color) } else {
now.to_string().normal() };
let args_color = match self.color_config.arg_color {
ArgColor::Fixed(arg_color) => format!("{}", args).color(arg_color), ArgColor::LogLevel => match level {
Level::Error => format!("{}", args).color(self.color_config.error_color),
Level::Warn => format!("{}", args).color(self.color_config.warn_color),
Level::Info => format!("{}", args).color(self.color_config.info_color),
Level::Debug => format!("{}", args).color(self.color_config.debug_color),
Level::Trace => format!("{}", args).color(self.color_config.trace_color),
},
ArgColor::None => format!("{}", args).normal(), };
let colorized_log = match level {
Level::Error => format!(
"[{}] {:<5} {}: {}",
colored_now, "ERROR".color(self.color_config.error_color), target.color(self.color_config.target_color), args_color ),
Level::Warn => format!(
"[{}] {:<5} {}: {}",
colored_now, "WARN".color(self.color_config.warn_color), target.color(self.color_config.target_color), args_color ),
Level::Info => format!(
"[{}] {:<5} {}: {}",
colored_now, "INFO".color(self.color_config.info_color), target.color(self.color_config.target_color), args_color ),
Level::Debug => format!(
"[{}] {:<5} {}: {}",
colored_now, "DEBUG".color(self.color_config.debug_color), target.color(self.color_config.target_color), args_color ),
Level::Trace => format!(
"[{}] {:<5} {}: {}",
colored_now, "TRACE".color(self.color_config.trace_color), target.color(self.color_config.target_color), args_color ),
};
match level {
Level::Error | Level::Warn => eprintln!("{}", colorized_log),
_ => println!("{}", colorized_log),
}
if let Some(file) = &self.log_file {
let mut file = file.lock().unwrap();
writeln!(file, "[{}] {:<5}: {}", now, level, args).expect("Failed to write to log file");
}
}
}
fn flush(&self) {}
}