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 {
pub fn new() -> Self {
Self {
level: Level::Trace,
color: false,
target: None,
}
}
pub fn level(mut self, level: Level) -> Self {
self.level = level;
self
}
pub fn level_from_default_env(self) -> Result<Self, LogsError> {
self.level_from_env("LOG")
}
pub fn level_from_env<S: AsRef<str>>(mut self, name: S) -> Result<Self, LogsError> {
match env::var(name.as_ref()) {
Ok(s) => match Level::from_str(&s) {
Ok(level) => {
self.level = level;
Ok(self)
}
Err(err) => Err(LogsError::Level(err)),
},
Err(err) => Err(LogsError::Env(err)),
}
}
pub fn color(mut self, color: bool) -> Self {
self.color = color;
self
}
pub fn target<S: ToString>(mut self, target: S) -> Self {
self.target = Some(target.to_string());
self
}
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",
}
}
}