#![deny(missing_docs)]
use std::io;
use std::io::{stderr, stdout, Write};
use std::str::FromStr;
use log::{Level, Log, Metadata, Record};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use atty::Stream;
use std::time::Instant;
#[derive(Clone)]
pub struct SimpleLogger {
log_level: Level,
prefix: bool,
start: Instant,
timestamp: bool,
}
const DEFAULT_LOG_LEVEL: Level = Level::Error;
impl SimpleLogger {
pub fn init(verbosity: Option<&str>) {
Self::init_prefix(verbosity, true)
}
pub fn init_prefix(verbosity: Option<&str>, prefix: bool) {
Self::init_prefix_timestamp(verbosity, prefix, false);
}
pub fn init_prefix_timestamp(verbosity: Option<&str>, prefix: bool, timestamp: bool) {
let log_level = parse_log_level(verbosity);
let simplogger = SimpleLogger {
log_level,
prefix,
start: Instant::now(),
timestamp,
};
let logger = Box::new(simplogger);
let _ = log::set_boxed_logger(logger);
log::set_max_level(log_level.to_level_filter());
}
}
fn parse_log_level(arg: Option<&str>) -> Level {
match arg {
None => DEFAULT_LOG_LEVEL,
Some(arg) => match Level::from_str(arg) {
Ok(ll) => ll,
Err(_) => DEFAULT_LOG_LEVEL
}
}
}
impl Log for SimpleLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.log_level
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
let message = if self.prefix {
format!("{}\t- {}", record.level(), record.args())
} else {
format!("{}", record.args())
};
if atty::is(Stream::Stdout) {
match record.level() {
Level::Error => stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red))).unwrap(),
Level::Warn => stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow))).unwrap(),
Level::Info=> stdout.set_color(ColorSpec::new().set_fg(Some(Color::Magenta))).unwrap(),
Level::Debug=> stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue))).unwrap(),
Level::Trace=> stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))).unwrap()
}
}
if self.timestamp {
let stdout = io::stdout();
let mut handle = stdout.lock();
let _ = handle.write_all(
format!("{:?} {}\n", self.start.elapsed(), message).as_bytes());
} else {
let stdout = io::stdout();
let mut handle = stdout.lock();
let _ = handle.write_all(
format!("{}\n", message).as_bytes());
}
}
}
fn flush(&self) {
stdout().flush().unwrap();
stderr().flush().unwrap();
}
}
#[cfg(test)]
mod test {
use log::Level;
use super::SimpleLogger;
#[test]
fn no_log_level_arg() {
assert_eq!(super::parse_log_level(None), super::DEFAULT_LOG_LEVEL);
}
#[test]
fn invalid_log_level_arg() {
assert_eq!(super::parse_log_level(Some("garbage")), super::DEFAULT_LOG_LEVEL);
}
#[test]
fn info_log_level_arg() {
assert_eq!(super::parse_log_level(Some("INFO")), Level::Info);
}
#[test]
fn error_log_level_arg() {
assert_eq!(super::parse_log_level(Some("ERROR")), Level::Error);
}
#[test]
fn parse_debug_log_level_arg() {
assert_eq!(super::parse_log_level(Some("DEBUG")), Level::Debug);
assert_eq!(super::parse_log_level(Some("debug")), Level::Debug);
}
#[test]
fn init_legacy_no_levels() {
SimpleLogger::init(None);
}
#[test]
fn init_legacy_debug_level() {
SimpleLogger::init(Some("DEBUG"));
}
#[test]
fn init_no_level_no_prefix() {
SimpleLogger::init_prefix(None, false);
}
}