use std::{
env,
io::{self, IsTerminal, Write},
sync::Arc,
};
use log::SetLoggerError;
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
pub const MODULE_PATH_UNKNOWN: &str = "?";
pub const MODULE_LINE_UNKNOWN: &str = "?";
#[derive(Debug)]
pub struct Logger {
level: log::Level,
writer: Arc<BufferWriter>,
}
impl Logger {
pub fn new() -> Logger {
let no_color = env::var("NO_COLOR");
let force_color = env::var("FORCE_COLOR");
let color_choice = if no_color.is_ok() && !no_color.unwrap().is_empty() {
ColorChoice::Never
} else if force_color.is_ok() && !force_color.unwrap().is_empty() {
ColorChoice::Always
} else if std::io::stderr().is_terminal() {
ColorChoice::Auto
} else {
ColorChoice::Never
};
Logger {
level: log::Level::Info,
writer: Arc::new(BufferWriter::stderr(color_choice)),
}
}
pub fn level(mut self, level: log::Level) -> Self {
self.level = level;
self
}
pub fn init(self) -> Result<(), SetLoggerError> {
log::set_max_level(self.level.to_level_filter());
log::set_boxed_logger(Box::new(self))
}
fn log_with_level(&self, record: &log::Record) -> io::Result<()> {
let level = record.level();
let mut buffer = self.writer.buffer();
if let Some(color) = color(level) {
buffer.set_color(ColorSpec::new().set_fg(Some(color)))?;
}
if !matches!(level, log::Level::Info) {
write!(buffer, "{}: ", level.to_string().to_lowercase())?;
}
buffer.reset()?;
writeln!(buffer, "{}", record.args())?;
self.writer.print(&buffer)
}
fn log_with_trace(&self, record: &log::Record) -> io::Result<()> {
let path = record.module_path().unwrap_or(MODULE_PATH_UNKNOWN);
let line = if let Some(l) = record.line() {
l.to_string()
} else {
MODULE_LINE_UNKNOWN.to_string()
};
let level = record.level();
let mut buffer = self.writer.buffer();
if let Some(color) = color(level) {
buffer.set_color(ColorSpec::new().set_fg(Some(color)))?;
}
write!(
buffer,
"{}({}): {}: ",
path,
line,
level.to_string().to_lowercase()
)?;
buffer.reset()?;
writeln!(buffer, "{}", record.args())?;
self.writer.print(&buffer)
}
}
impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
match self.level {
log::Level::Trace => self
.log_with_trace(record)
.expect("Failed to log with trace"),
_ => self.log_with_level(record).expect("Failed to log"),
}
}
}
fn flush(&self) {
}
}
impl Default for Logger {
fn default() -> Logger {
Logger::new()
}
}
pub fn init(level: log::Level) -> Result<(), SetLoggerError> {
Logger::new().level(level).init()
}
fn color(level: log::Level) -> Option<Color> {
match level {
log::Level::Error => Some(Color::Red),
log::Level::Warn => Some(Color::Yellow),
log::Level::Info => None,
log::Level::Debug => Some(Color::Blue),
log::Level::Trace => Some(Color::Magenta),
}
}