logs/
lib.rs

1//! # Usage
2//! ```rust
3//! use logs::{debug, error, info, trace, warn, Logs};
4//!
5//! Logs::new().init();
6//! trace!("This is a trace log");
7//! debug!("This is a debug log");
8//! info!("This is a info log");
9//! warn!("This is a warn log");
10//! error!("This is a error log");
11//! ```
12//! Output:
13//! ```ignore
14//! 2022-09-06T08:38:23.490 [TRACE] This is a trace log
15//! 2022-09-06T08:38:23.490 [DEBUG] This is a debug log
16//! 2022-09-06T08:38:23.490 [INFO ] This is a info log
17//! 2022-09-06T08:38:23.490 [WARN ] This is a warn log
18//! 2022-09-06T08:38:23.490 [ERROR] This is a error log
19//! ```
20//! ## Options
21//! ```ignore
22//! use logs::{Logs, debug, error, LevelFilter};
23//! Logs::new()
24//!     // Show log level color
25//!     .color(true)
26//!     // Filter log target
27//!     .target("target")
28//!     // Filter log level
29//!     .level(LevelFilter::Info)
30//!     // Filter log target from `LOG` environment variable
31//!     .level_from_default_env()
32//!     .unwrap()
33//!     // Filter log target from `NAME` environment variable
34//!     .level_from_env("NAME")
35//!     .unwrap()
36//!     // Filter log level from str
37//!     .level_from_str("info")
38//!     .unwrap()
39//!     // Apply
40//!     .init();
41//! ```
42
43pub use log::{debug, error, info, trace, warn, LevelFilter};
44use log::{Level, Log, Metadata, ParseLevelError, Record};
45use std::{
46    env::{self, VarError},
47    str::FromStr,
48};
49use time::{format_description::FormatItem, OffsetDateTime};
50
51const TIMESTAMP_FORMAT_OFFSET: &[FormatItem] = time::macros::format_description!(
52    "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]"
53);
54
55/// Logger config
56#[derive(Debug, Clone)]
57pub struct Logs {
58    level: LevelFilter,
59    color: bool,
60    target: Option<String>,
61}
62
63#[derive(Debug)]
64pub enum LogsError {
65    Level(ParseLevelError),
66    Env(VarError),
67}
68
69impl Default for Logs {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl Logs {
76    /// Create a new logs
77    pub fn new() -> Self {
78        Self {
79            level: LevelFilter::Trace,
80            color: false,
81            target: None,
82        }
83    }
84
85    /// Set log level color
86    pub fn color(mut self, color: bool) -> Self {
87        self.color = color;
88        self
89    }
90
91    /// Filter log target
92    pub fn target<S: AsRef<str>>(mut self, target: S) -> Self {
93        let target = target.as_ref().replace('-', "_");
94        self.target = Some(target);
95        self
96    }
97
98    /// Filter log level
99    pub fn level(mut self, level: LevelFilter) -> Self {
100        self.level = level;
101        self
102    }
103
104    /// Filter log level from `LOG` environment variable
105    pub fn level_from_default_env(self) -> Result<Self, LogsError> {
106        self.level_from_env("LOG")
107    }
108
109    /// Filter log level from `NAME` environment variable
110    pub fn level_from_env<S: AsRef<str>>(self, name: S) -> Result<Self, LogsError> {
111        match env::var(name.as_ref()) {
112            Ok(s) => self.level_from_str(&s),
113            Err(err) => Err(LogsError::Env(err)),
114        }
115    }
116
117    /// Filter log level from `str`
118    pub fn level_from_str<S: AsRef<str>>(mut self, s: S) -> Result<Self, LogsError> {
119        match LevelFilter::from_str(s.as_ref()) {
120            Ok(level) => {
121                self.level = level;
122                Ok(self)
123            }
124            Err(err) => Err(LogsError::Level(err)),
125        }
126    }
127
128    /// log init
129    pub fn init(self) {
130        let rst =
131            log::set_boxed_logger(Box::new(self)).map(|()| log::set_max_level(LevelFilter::Trace));
132        if let Err(err) = rst {
133            panic!("log config failed {:#?}", err);
134        }
135    }
136}
137
138impl Log for Logs {
139    fn enabled(&self, metadata: &Metadata) -> bool {
140        if let Some(level) = self.level.to_level() {
141            if level >= metadata.level() {
142                return match &self.target {
143                    Some(t) => metadata.target().starts_with(t),
144                    None => true,
145                };
146            }
147        }
148        false
149    }
150
151    fn log(&self, record: &Record) {
152        if self.enabled(record.metadata()) {
153            let datetime = OffsetDateTime::now_utc()
154                .format(&TIMESTAMP_FORMAT_OFFSET)
155                .unwrap();
156            let level = level_to_str(record.level(), self.color);
157
158            println!("{} [{}] {}", datetime, level, record.args());
159        }
160    }
161
162    fn flush(&self) {}
163}
164
165fn level_to_str(level: Level, color: bool) -> &'static str {
166    if color {
167        match level {
168            Level::Error => "\x1B[31mERROR\x1B[0m",
169            Level::Warn => "\x1B[33mWARN \x1B[0m",
170            Level::Info => "\x1B[32mINFO \x1B[0m",
171            Level::Debug => "\x1B[3;34mDEBUG\x1B[0m",
172            Level::Trace => "\x1B[2;3mTRACE\x1B[0m",
173        }
174    } else {
175        match level {
176            Level::Error => "ERROR",
177            Level::Warn => "WARN ",
178            Level::Info => "INFO ",
179            Level::Debug => "DEBUG",
180            Level::Trace => "TRACE",
181        }
182    }
183}