use log::{Level, LevelFilter, Log};
use std::cell::Cell;
use std::collections::HashSet;
use std::sync::Mutex;
use termcolor::Color;
use time::UtcOffset;
use crate::target::{OutputTargetImpl, WriteExt};
const TIME_FORMAT_DESCRIPTION: &[time::format_description::FormatItem] =
time::macros::format_description!("[hour]:[minute]:[second]");
thread_local! {
static IS_REENTRANT_LOGGING_CALL: Cell<bool> = Cell::new(false);
}
pub struct Logger {
pub max_log_level: LevelFilter,
pub always_show_module_path: bool,
pub local_time_offset: UtcOffset,
pub output_target: Mutex<OutputTargetImpl>,
pub module_blacklist: HashSet<String>,
}
impl Logger {
pub fn target_enabled(&self, target: &str) -> bool {
if let Some((crate_name, _)) = target.split_once(':') {
if self.module_blacklist.contains(crate_name) {
return false;
}
}
!self.module_blacklist.contains(target)
}
fn do_log(&self, mut writer: &mut dyn WriteExt, record: &log::Record) {
let current_time = time::OffsetDateTime::now_utc().to_offset(self.local_time_offset);
let _ = current_time.format_into(&mut writer, TIME_FORMAT_DESCRIPTION);
match record.level() {
log::Level::Error => {
writer.set_fg_color(Color::Red);
let _ = write!(writer, " [ERROR] ");
writer.reset_colors();
}
log::Level::Warn => {
writer.set_fg_color(Color::Yellow);
let _ = write!(writer, " [WARN] ");
writer.reset_colors();
}
log::Level::Info => {
writer.set_fg_color(Color::Blue);
let _ = write!(writer, " [INFO] ");
writer.reset_colors();
}
log::Level::Debug => {
writer.set_fg_color(Color::Cyan);
let _ = write!(writer, " [DEBUG] ");
writer.reset_colors();
}
log::Level::Trace => {
let _ = write!(writer, " [TRACE] ");
}
}
if record.level() >= Level::Debug {
let current_thread = std::thread::current();
let id = format!("{:?}", current_thread.id());
let id = id
.strip_prefix("ThreadId(")
.and_then(|id| id.strip_suffix(')'))
.unwrap_or(&id);
let _ = match current_thread.name() {
Some(name) if name != "main" => write!(writer, "({id}, {name})"),
_ => write!(writer, "({id})"),
};
if let Some(module_path) = record.module_path() {
let _ = write!(writer, " {}", module_path);
}
let _ = write!(writer, ": ");
} else if self.always_show_module_path {
if let Some(module_path) = record.module_path() {
let _ = write!(writer, "{}: ", module_path);
}
}
if record.level() >= Level::Trace {
let _ = match (record.file(), record.line()) {
(Some(file), Some(line)) => write!(writer, "[{file}:{line}] "),
(Some(file), None) => write!(writer, "[{file}] "),
_ => Ok(()),
};
}
let _ = writeln!(writer, "{}", record.args());
let _ = writer.flush();
}
}
impl Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.max_log_level && !self.target_enabled(metadata.target())
}
fn log(&self, record: &log::Record) {
if !self.target_enabled(
record
.module_path()
.unwrap_or_else(|| record.metadata().target()),
) {
return;
}
IS_REENTRANT_LOGGING_CALL.with(|is_reentrant_logging_call| {
if is_reentrant_logging_call.get() {
let mut target = OutputTargetImpl::default_from_environment();
self.do_log(target.writer(), record);
} else {
is_reentrant_logging_call.set(true);
let mut target = match self.output_target.lock() {
Ok(target) => target,
Err(err) => err.into_inner(),
};
self.do_log(target.writer(), record);
is_reentrant_logging_call.set(false);
}
});
}
fn flush(&self) {
let _ = self
.output_target
.lock()
.expect("Mutex poisoned")
.writer()
.flush();
}
}