use std::fmt;
use std::io::{self, Write};
use std::sync::Mutex;
use ansi_term::Color;
use fern::{Dispatch, FormatCallback};
use lazy_static::lazy_static;
use log::{Level, LevelFilter, SetLoggerError};
pub use fern;
pub use log;
lazy_static! {
static ref GLOBAL_CONTEXT: Mutex<Option<String>> = Mutex::new(None);
static ref OUTPUT_MUTEX: Mutex<()> = Mutex::new(());
}
pub struct GlobalContext {
}
impl GlobalContext {
pub fn new(name: &str) -> GlobalContext {
let context_string = format!("[{}] ", name);
{
let mut context = GLOBAL_CONTEXT.lock().unwrap();
if context.is_some() {
panic!("An attempt to set a nested global context");
}
*context = Some(context_string);
}
GlobalContext{}
}
fn get() -> String {
GLOBAL_CONTEXT.lock().unwrap().as_ref().map(Clone::clone).unwrap_or_default()
}
}
impl Drop for GlobalContext {
fn drop(&mut self) {
*GLOBAL_CONTEXT.lock().unwrap() = None;
}
}
pub fn init(module_name: &'static str, level: Level) -> Result<(), SetLoggerError> {
builder(module_name, level).apply()
}
pub fn builder(module_name: &'static str, level: Level) -> Dispatch {
let debug_mode = level >= Level::Debug;
let stdout_dispatcher =
configure_formatter(Dispatch::new(), debug_mode, atty::is(atty::Stream::Stdout))
.filter(|metadata| {metadata.level() >= Level::Info})
.chain(io::stdout());
let stderr_dispatcher =
configure_formatter(Dispatch::new(), debug_mode, atty::is(atty::Stream::Stderr))
.filter(|metadata| {metadata.level() < Level::Info})
.chain(io::stderr());
Dispatch::new()
.level(if debug_mode {
LevelFilter::Warn
} else {
LevelFilter::Off
})
.level_for(module_name, level.to_level_filter())
.chain(stdout_dispatcher)
.chain(stderr_dispatcher)
}
fn configure_formatter(dispatcher: Dispatch, debug_mode: bool, colored_output: bool) -> Dispatch {
if debug_mode {
dispatcher.format(move |out, message, record| {
let level = record.level();
let level_name = get_level_name(level);
let time = chrono::Local::now().format("[%T%.3f]");
let file = if let (Some(mut file), Some(line)) = (record.file(), record.line()) {
let mut file_width = 10;
let mut line_width = 3;
let mut line_extra_width = line / 1000;
while line_extra_width > 0 && file_width > 0 {
line_width += 1;
file_width -= 1;
line_extra_width /= 10;
}
if file.starts_with("src/") {
file = &file[4..];
}
if file.len() > file_width {
file = &file[file.len() - file_width..]
}
format!(" [{file:>file_width$}:{line:0line_width$}]",
file=file, file_width=file_width, line=line, line_width=line_width)
} else {
String::new()
};
if colored_output {
let level_color = get_level_color(level);
write_log(out, level, format_args!(
"{color_prefix}{time}{file} {level}: {context}{message}{color_suffix}",
color_prefix=level_color.prefix(), time=time, file=file, level=level_name,
context=GlobalContext::get(), message=message, color_suffix=level_color.suffix()
));
} else {
write_log(out, level, format_args!(
"{time}{file} {level}: {context}{message}",
time=time, file=file, level=level_name, context=GlobalContext::get(),
message=message
));
}
})
} else {
dispatcher.format(move |out, message, record| {
let level = record.level();
let level_name = get_level_name(level);
if colored_output {
let level_color = get_level_color(level);
write_log(out, level, format_args!(
"{color_prefix}{level}: {context}{message}{color_suffix}",
color_prefix=level_color.prefix(), level=level_name,
context=GlobalContext::get(), message=message, color_suffix=level_color.suffix()
));
} else {
write_log(out, level, format_args!("{level}: {context}{message}",
level=level_name, context=GlobalContext::get(), message=message));
}
})
}
}
fn get_level_color(level: Level) -> Color {
match level {
Level::Error => Color::Red,
Level::Warn => Color::Yellow,
Level::Info => Color::Green,
Level::Debug => Color::Cyan,
Level::Trace => Color::Purple,
}
}
fn get_level_name(level: Level) -> &'static str {
match level {
Level::Error => "E",
Level::Warn => "W",
Level::Info => "I",
Level::Debug => "D",
Level::Trace => "T",
}
}
fn write_log(out: FormatCallback, level: Level, formatted_message: fmt::Arguments) {
let _lock = OUTPUT_MUTEX.lock();
out.finish(formatted_message);
let _ = if level >= Level::Info {
io::stdout().flush()
} else {
io::stderr().flush()
};
}