extern crate hashbrown;
extern crate log;
use hashbrown::{HashMap, hash_map::Entry};
use log::{LevelFilter, Log, Metadata, Record};
use std::borrow::Cow;
use std::fs::{File, OpenOptions};
use std::io::Write;
type Formatter = Fn(&log::Record) -> String + Sync + Send + 'static;
fn get_parent_module(target: &str) -> &str {
let index = target.rfind("::")
.unwrap_or(target.len());
&target[..index]
}
fn prepare_log_file(path: String) -> File {
let log_file = OpenOptions::new()
.append(true)
.create(true)
.open(path);
match log_file {
Ok(f) => f,
Err(e) => panic!("Failed to open log file: {}", e)
}
}
pub enum Output {
Stdout,
Stderr,
File(String)
}
pub struct Logger {
default_level: LevelFilter,
filters: HashMap<Cow<'static, str>, LevelFilter>,
formatter: Option<Box<Formatter>>,
output_dst: Output
}
impl Logger {
pub fn new() -> Self {
Logger {
default_level: LevelFilter::Info,
filters: HashMap::new(),
formatter: None,
output_dst: Output::Stdout
}
}
pub fn for_module<T: Into<Cow<'static, str>>>(mut self, module: T, level: LevelFilter) -> Self {
let module = module.into();
match self.filters.entry(module) {
Entry::Occupied(mut o) => {
o.insert(level);
},
Entry::Vacant(v) => {
v.insert(level);
}
};
self
}
pub fn format<F>(mut self, formatter: F) -> Self where
F: Fn(&log::Record) -> String + Sync + Send + 'static {
self.formatter = Some(Box::new(formatter));
self
}
pub fn get_max_level(&self) -> LevelFilter {
self.filters.values()
.fold(self.default_level, |acc, e| {
acc.max(*e)
})
}
pub fn ready(self) -> Result<(), log::SetLoggerError> {
let max_level = self.get_max_level();
let log = Box::new(self);
log::set_boxed_logger(log)?;
log::set_max_level(max_level);
Ok(())
}
fn resolve_module_level(&self, target: &str) -> &LevelFilter {
if self.filters.contains_key(target) {
self.filters.get(target)
.unwrap_or(&self.default_level)
} else {
let key = format!("{}::*", get_parent_module(target));
if target.contains("::") && self.filters.contains_key(&key[..]) {
self.filters.get(&key[..])
.unwrap_or(&self.default_level)
} else {
&self.default_level
}
}
}
pub fn set_default_level(mut self, level: LevelFilter) -> Self {
self.default_level = level;
self
}
}
impl Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
let metadata_filter = metadata.level()
.to_level_filter();
let target_filter = self.resolve_module_level(metadata.target());
&metadata_filter <= target_filter
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut message = format!("[{}] {}", record.level(), record.args());
if let Some(formatter) = &self.formatter {
message = format!("{}", formatter(record));
}
match &self.output_dst {
Output::Stderr => {
eprintln!("{}", message);
},
Output::Stdout => {
println!("{}", message);
},
Output::File(path) => {
let mut file = prepare_log_file(path.to_owned());
if let Err(e) = writeln!(file, "{}", message) {
panic!("Failed to write to log file: {}", e);
}
self.flush();
}
}
}
}
fn flush(&self) {
if let Output::File(path) = &self.output_dst {
let mut file = prepare_log_file(path.to_owned());
if let Err(e) = file.flush() {
panic!("Failed to flush the log file: {}", e);
}
}
}
}