use std::{io, path::Path, sync::OnceLock};
use crossterm::{
execute,
style::{Color, Print, ResetColor, SetForegroundColor},
};
#[derive(Debug)]
pub struct CliLogger {
localize_to: Option<&'static str>,
}
impl CliLogger {
pub fn init(max_level: log::LevelFilter, localize_to: Option<&'static str>) {
static LOGGER: OnceLock<CliLogger> = OnceLock::new();
let _ = log::set_logger(LOGGER.get_or_init(|| CliLogger { localize_to }));
log::set_max_level(max_level);
}
}
impl log::Log for CliLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level().to_level_filter() <= log::max_level()
&& (self
.localize_to
.is_none_or(|target| metadata.target().starts_with(target)))
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
let message = match record.level() {
log::Level::Info => format!("[INFO]: {}\n", record.args()),
level => {
if let Some((file, line)) = record
.file()
.and_then(|s| Path::new(s).file_name())
.and_then(|s| s.to_str())
.zip(record.line())
{
format!(
"[{level}][in {file} at line {line}]: {msg}\n",
msg = record.args()
)
} else {
format!("[{level}]: {msg}\n", msg = record.args())
}
}
};
let color = match record.level() {
log::Level::Warn => Some(Color::Yellow),
log::Level::Debug => Some(Color::Blue),
log::Level::Trace => Some(Color::Green),
_ => None,
};
match record.level() {
log::Level::Error => {
let _ = crossterm::execute!(
io::stderr(),
SetForegroundColor(Color::Red),
Print(message),
ResetColor
);
}
_ => {
if let Some(color) = color {
let _ = crossterm::execute!(
io::stdout(),
SetForegroundColor(color),
Print(message),
ResetColor
);
} else {
let _ = execute!(io::stderr(), Print(message));
}
}
}
}
}
fn flush(&self) {}
}