1pub 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#[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 pub fn new() -> Self {
78 Self {
79 level: LevelFilter::Trace,
80 color: false,
81 target: None,
82 }
83 }
84
85 pub fn color(mut self, color: bool) -> Self {
87 self.color = color;
88 self
89 }
90
91 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 pub fn level(mut self, level: LevelFilter) -> Self {
100 self.level = level;
101 self
102 }
103
104 pub fn level_from_default_env(self) -> Result<Self, LogsError> {
106 self.level_from_env("LOG")
107 }
108
109 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 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 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}