1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use ansi_term::Color;
use log::{Level, Log, Metadata, Record, SetLoggerError};
use std::borrow::Borrow;
use std::fmt;
use std::time::{Duration, Instant};
// Duration formatting
#[derive(Clone, Copy, Debug)]
pub struct TimeFormat<T: Borrow<Duration>>(pub T);
impl<T: Borrow<Duration>> fmt::Display for TimeFormat<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let dur: &Duration = self.0.borrow();
        // The format string pads the fractional part out to 3 decimals.
        if dur.as_secs() > 0 {
            write!(f, "{}.{:0>3}s", dur.as_secs(), dur.subsec_millis(),)
        } else if dur.subsec_millis() > 0 {
            let integral = dur.subsec_millis();
            write!(
                f,
                "{}.{:0>3}ms",
                integral,
                dur.subsec_micros() - integral * 1000
            )
        } else if dur.subsec_micros() > 0 {
            let integral = dur.subsec_micros();
            write!(
                f,
                "{}.{:0>3}µs",
                integral,
                dur.subsec_nanos() - integral * 1000
            )
        } else {
            write!(f, "{:6}ns", dur.subsec_nanos())
        }
    }
}

struct ColorLevel(Level);
impl fmt::Display for ColorLevel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            Level::Trace => Color::Purple.paint("TRACE"),
            Level::Debug => Color::Blue.paint("DEBUG"),
            Level::Info => Color::Green.paint("INFO "),
            Level::Warn => Color::Yellow.paint("WARN "),
            Level::Error => Color::Red.paint("ERROR"),
        }
        .fmt(f)
    }
}

struct ScrubLog {
    init_time: Instant,
    filter: env_logger::filter::Filter,
}
impl ScrubLog {
    fn from_filter(filter_string: &str) -> ScrubLog {
        use env_logger::filter::Builder;
        let mut builder = Builder::new();
        if !filter_string.is_empty() {
            builder.parse(filter_string);
        }
        ScrubLog {
            init_time: Instant::now(),
            filter: builder.build(),
        }
    }
}

impl Log for ScrubLog {
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.filter.enabled(metadata)
    }

    fn log(&self, record: &Record) {
        if !self.filter.matches(record) {
            return;
        }
        let line_string = match record.line() {
            Some(x) => format!("{}", x),
            None => String::from("-"),
        };

        let timestamp = Instant::now() - self.init_time;
        println!(
            "{}]{} [{}:{}] {}",
            ColorLevel(record.level()),
            TimeFormat(timestamp),
            record.module_path().unwrap_or("-"),
            line_string,
            record.args()
        )
    }

    fn flush(&self) {}
}

pub fn init_with_filter_string(filter_string: &str) -> Result<(), SetLoggerError> {
    let logger = ScrubLog::from_filter(filter_string);
    let log_level = logger.filter.filter();
    log::set_boxed_logger(Box::new(logger))?;
    log::set_max_level(log_level);
    Ok(())
}

pub fn init() -> Result<(), SetLoggerError> {
    init_with_filter_string("")
}

#[cfg(test)]
mod tests {
    use super::*;
    use log::{debug, error, info, trace, warn};
    #[test]
    fn smoke() {
        init().unwrap();
        trace!("A");
        debug!("B");
        info!("C");
        warn!("D");
        error!("E");
    }
}