logs 0.6.3

A simple terminal logger
Documentation
pub use log::{debug, error, info, trace, warn, Level};
use log::{LevelFilter, Log, Metadata, ParseLevelError, Record};
use std::{
    env::{self, VarError},
    str::FromStr,
};
use time::{format_description::FormatItem, OffsetDateTime};

const TIMESTAMP_FORMAT_OFFSET: &[FormatItem] = time::macros::format_description!(
    "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]"
);

#[derive(Debug, Clone)]
pub struct Logs {
    level: Level,
    color: bool,
    target: Option<String>,
}

#[derive(Debug)]
pub enum LogsError {
    Level(ParseLevelError),
    Env(VarError),
}

impl Default for Logs {
    fn default() -> Self {
        Self::new()
    }
}

impl Logs {
    /// Create a new logs
    pub fn new() -> Self {
        Self {
            level: Level::Trace,
            color: false,
            target: None,
        }
    }

    /// Set log level color
    pub fn color(mut self, color: bool) -> Self {
        self.color = color;
        self
    }

    /// Filter log target
    pub fn target<S: AsRef<str>>(mut self, target: S) -> Self {
        let target = target.as_ref().replace('-', "_");
        self.target = Some(target);
        self
    }

    /// Filter log level
    pub fn level(mut self, level: Level) -> Self {
        self.level = level;
        self
    }

    /// Filter log level from `LOG` environment variable
    pub fn level_from_default_env(self) -> Result<Self, LogsError> {
        self.level_from_env("LOG")
    }

    /// Filter log level from `NAME` environment variable
    pub fn level_from_env<S: AsRef<str>>(self, name: S) -> Result<Self, LogsError> {
        match env::var(name.as_ref()) {
            Ok(s) => self.level_from_str(&s),
            Err(err) => Err(LogsError::Env(err)),
        }
    }

    /// Filter log level from `str`
    pub fn level_from_str<S: AsRef<str>>(mut self, s: S) -> Result<Self, LogsError> {
        match Level::from_str(s.as_ref()) {
            Ok(level) => {
                self.level = level;
                Ok(self)
            }
            Err(err) => Err(LogsError::Level(err)),
        }
    }

    /// log init
    pub fn init(self) {
        let rst =
            log::set_boxed_logger(Box::new(self)).map(|()| log::set_max_level(LevelFilter::Trace));
        if let Err(err) = rst {
            panic!("log config failed {:#?}", err);
        }
    }
}

impl Log for Logs {
    fn enabled(&self, metadata: &Metadata) -> bool {
        if self.level >= metadata.level() {
            return match &self.target {
                Some(t) => t == metadata.target(),
                None => true,
            };
        }
        false
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            let datetime = OffsetDateTime::now_utc()
                .format(&TIMESTAMP_FORMAT_OFFSET)
                .unwrap();
            let level = level_to_str(record.level(), self.color);

            println!("{} [{}] {}", datetime, level, record.args());
        }
    }

    fn flush(&self) {}
}

fn level_to_str(level: Level, color: bool) -> &'static str {
    if color {
        match level {
            Level::Error => "\x1B[31mERROR\x1B[0m",
            Level::Warn => "\x1B[33mWARN \x1B[0m",
            Level::Info => "\x1B[32mINFO \x1B[0m",
            Level::Debug => "\x1B[3;34mDEBUG\x1B[0m",
            Level::Trace => "\x1B[2;3mTRACE\x1B[0m",
        }
    } else {
        match level {
            Level::Error => "ERROR",
            Level::Warn => "WARN ",
            Level::Info => "INFO ",
            Level::Debug => "DEBUG",
            Level::Trace => "TRACE",
        }
    }
}