easy_logger/
lib.rs

1use log::{self, Log, Metadata, Record, SetLoggerError, LevelFilter, Level};
2use time::{self, OffsetDateTime, format_description, error};
3use colored::{Color, Colorize };
4
5use std::collections::HashMap;
6// use std::cell::RefCell;
7use std::sync::Mutex;
8use std::fs::{File, OpenOptions};
9use std::io::Write;
10use std::io::ErrorKind;
11const DE_TIME_PASE_FORMAT:&str = "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
12sign:mandatory]:[offset_minute]:[offset_second]";
13
14/// this can work and be set only before call init();
15/// in (Color, Color) first Color and second Color are font color and bg color respectively
16/// at the present, We can only use the enum from colored library
17#[derive(Debug)]
18struct ColorOption {
19    use_color: bool,
20    level_to_color: HashMap<Level, (Color,Color)>
21}
22
23impl ColorOption {//maybe I can extract some method and turn those into a Trait 
24    fn new(use_color: bool, level_to_color: HashMap<Level, (Color,Color)>) -> Self {
25        ColorOption {
26            use_color,
27            level_to_color
28        }
29    }
30    fn set_use_color(&mut self, use_color: bool) {
31        if use_color != self.use_color {
32            self.use_color = use_color;
33        }
34    }
35    fn get_use_color(&self) -> bool {
36        self.use_color
37    }
38    fn get_color_from_level(&self, l: Level) -> (Color,Color) {
39        match self.level_to_color.get(&l) {
40            Some(c) => *c,
41            None => panic!("get_color_from_level error !!!") //未来解决Error问题
42        }
43    }
44}
45
46#[derive(Debug)]
47struct TimeOption {
48    time_parse_formt: String,
49    /// if not utc, the only another one opt. is local_time
50    /// default is local_time(true)
51    use_local: bool,
52    /// default is true
53    log_time: bool
54}
55
56impl TimeOption {
57    fn new() -> Self {
58        TimeOption {
59            time_parse_formt: String::from(DE_TIME_PASE_FORMAT),
60            use_local: true,
61            log_time: true
62        }
63    }
64    ///get a formatted time String
65    fn get_time(&self) -> Result<String, error::Error>{//time 内置的 Error并没有做到处理
66        if self.enabled() {
67            let now = if self.use_local {
68                OffsetDateTime::now_local()?
69            } else {
70                OffsetDateTime::now_utc()
71            };
72            let time_format = format_description::parse(&self.time_parse_formt)?;
73            Ok(now.format(&time_format)?)
74        } else {
75            Ok(String::new())
76        }
77    }
78    fn set_time_format(&mut self, f: &str) {
79        self.time_parse_formt = String::from(f);
80    }
81    fn enabled(&self) -> bool {
82        self.log_time
83    }
84    fn set_use_local(&mut self, is_use: bool) {
85        if is_use != self.use_local {
86            self.use_local = is_use
87        }
88    }
89    fn set_log_time(&mut self, is_open: bool) {
90        if is_open != self.log_time {
91            self.log_time = is_open;
92        }
93    }
94    //解决时间生成错误的方案应该是重新生成时间
95    fn resolve_time_error(result: Result<String, error::Error>) -> String{//未来返回值可改为一个enum
96        match result {
97            Ok(t) => t,
98            Err(error::Error::IndeterminateOffset(e)) => {
99                eprintln!("{e}");
100                String::new()
101            },
102            _ => String::new()
103        }
104    }
105}
106
107
108///Destination Options
109/// in the future, the field in this struct may be changed in a muti-threads pattern
110#[derive(Debug)]
111struct DestOption {
112    ///default: use std out
113    use_dest: bool,
114    ///this option always combines with file option completing the flush feature
115    dest_out: Option<String>,
116    // file: Option<File>,
117    output_stream: String,
118    //第三项我们可以自定义flush,使得外界传入闭包生效
119    // custom_fn
120}
121impl DestOption {
122    fn new() -> Self {
123        DestOption {
124            use_dest: false,
125            dest_out: None,
126            // file: None,
127            output_stream: String::new()
128        }
129    }
130    /// setup with destination file
131    fn new_with_dest(dest: &str) -> Self {
132        DestOption {
133            use_dest: true,
134            dest_out: Some(String::from(dest)),
135            // file: None,
136            output_stream: String::new()
137        }
138    }
139    fn is_use_dest(&self) -> bool {
140        self.use_dest
141    }
142    fn set_use_dest(&mut self, is_open: bool) {
143        if self.use_dest != is_open {
144            self.use_dest = is_open;
145        }
146    }
147    fn push_output(&mut self, stream: &str) {
148        self.output_stream.push_str(stream);
149    }
150}
151#[derive(Debug)]
152pub struct ELogger{
153    time_option: TimeOption,
154    color_option: ColorOption,
155    dest: Mutex<DestOption> //use Mutex seems to be a error
156}
157impl Log for ELogger {
158    fn enabled(&self, _metadata: &Metadata) -> bool {
159        true
160    }
161    fn log(&self, record: &Record) {
162        let l = record.level(); //Level
163        
164        let (f_color, _) = self.get_color(l);
165        let l = format!("[{l}]"); //String Level
166
167        let time = self.get_time();
168        let output = format!("{}\r\n{}\r\n", time, record.args());
169
170        match self.is_use_dest() {
171            true => {
172                let output = format!("{} {}", l, output);
173                self.push_output(&output);
174                self.flush();
175            },
176            false => {
177                let l = l.color(f_color);
178                let output = format!("{} {}", l, output);
179                println!("{output}");
180            }
181        };
182            
183    }
184    fn flush(&self){
185        let mut guard = self.dest.lock().unwrap();
186        match &guard.dest_out {//only read, don't need to lock
187            Some(file_name) => {
188                let mut file = open_file(file_name);
189                let output = guard.output_stream.clone();
190                if let Ok(_) = file.write(output.as_bytes()) {
191                    // file.flush().expect("flush error");
192                };
193            },
194            None => panic!("can't read file without filename")//here we should make a panic 
195        };
196        guard.output_stream.clear();
197    }
198}
199fn open_file(file_name: &str) -> File {
200    match OpenOptions::new().append(true).open(file_name) {
201        Ok(f) => f,
202        Err(e) => match e.kind() {
203            ErrorKind::NotFound => {
204                create_file(file_name) //这里1.需要提示后进行生成2.直接生成
205            },
206            _ => panic!("unexpected error")
207        } 
208    }
209}
210
211fn create_file(file_name: &str) -> File {
212    match OpenOptions::new().create_new(true).append(true).open(file_name) {
213        Ok(f) => f,
214        Err(e) => match e.kind() {
215            ErrorKind::AlreadyExists => panic!("file already exists"),
216            _ => panic!("unexpected error")
217        }
218    }
219}
220/// you should have done all of initialization setup before calling init();
221impl ELogger {
222    pub fn new() -> Self {
223        //
224        let default_level = LevelFilter::Info;
225        log::set_max_level(default_level);
226        //
227        let default_color_map = HashMap::from([
228            (Level::Trace, (Color::White, Color::BrightBlack)),
229            (Level::Debug, (Color::BrightWhite, Color::Black)),
230            (Level::Info, (Color::BrightBlue, Color::Black)),
231            (Level::Warn, (Color::BrightYellow, Color::Black)),
232            (Level::Error, (Color::BrightRed, Color::Black)),
233        ]);
234        ELogger{
235            time_option: TimeOption::new(),
236            color_option: ColorOption::new(true, default_color_map),
237            dest: Mutex::new(DestOption::new())
238        }
239    }
240    // a shortcut
241    pub fn new_dest(dest: &str) -> Self {
242        let s = Self::new();
243        *s.dest.lock().unwrap() = DestOption::new_with_dest(dest);
244        s
245    }
246    ///  set the max Level, you can also use log::set_max_level
247    ///  but you can only get the max filter level by call log::max_level
248    ///  you can only input ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"] case 
249    ///  maybe I can cover more one layer upon the log::log! ?
250    ///  it's only the old impl, at that time, I want to implement a completely isolation.
251    //pub fn set_max_level(self, l: &str) -> Self {
252    //let l = match Level::from_str(l) {
253    //Ok(l) => l,
254    //Err(e) => panic!("you can only input ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'] case {e}")
255    //};
256    //log::set_max_level(l.to_level_filter());
257    //self
258    //}
259    pub fn set_max_level(self, l: Level) -> Self {
260        log::set_max_level(l.to_level_filter());
261        self
262    }
263
264    /// use your custom format to log the time
265    /// you can link to time crate <https://crates.io/crates/time> getting the introduction of time format
266    pub fn set_time_format(mut self, format: &str) -> Self {
267        self.time_option.set_time_format(format);
268        self
269    }
270    /// get the formatted time
271    pub fn get_time(&self) -> String {
272        TimeOption::resolve_time_error(self.time_option.get_time())
273    }
274    /// a switch for controlling your log time standard
275    pub fn set_use_local(mut self, is_use: bool) -> Self {
276        self.time_option.set_use_local(is_use);
277        self
278    } 
279    /// control whether log the time
280    pub fn set_log_time(mut self, is_open: bool) -> Self {
281        self.time_option.set_log_time(is_open);
282        self
283    }
284
285    ///enabled to use color format
286    pub fn enable_use_color(mut self) -> Self {
287        self.color_option.set_use_color(true);
288        self
289    }
290    ///disable to use color format
291    pub fn disable_use_color(mut self) -> Self {
292        self.color_option.set_use_color(false);
293        self
294    }
295    ///check use_color
296    pub fn is_use_color(&self) -> bool {
297        self.color_option.get_use_color()
298    }
299    ///get (Color, Color)
300    pub fn get_color(&self, l: Level) -> (Color,Color) {
301        self.color_option.get_color_from_level(l)
302    }
303
304    /// destination
305    pub fn is_use_dest(&self) -> bool {
306        self.dest.lock().unwrap().is_use_dest() //here I haven't resolved the unexpected err
307    }
308    /// push stream into
309    pub fn push_output(&self, stream: &str) {
310        self.dest.lock().unwrap().push_output(stream) // the same as above method
311    }
312    /// control whether use the
313    pub fn set_use_dest(self, is_open: bool) -> Self {
314        self.dest.lock().unwrap().set_use_dest(is_open);
315        self
316    }
317    #[must_use]
318    pub fn init(self) -> Result<(), SetLoggerError> {
319        log::set_boxed_logger(Box::new(self))
320    }
321}
322
323///only a logger init without any ohters
324/// # Examples
325/// ```
326/// use easy_logger::quick_init;
327/// use log::{log, Level};
328/// fn main() {
329///     quick_init();
330///     //the next line of code could be ignored, 
331///     //and default level will allow you to log message whose level greater than debug
332///     log::set_max_level(Level::Trace.to_level_filter());
333///     info!("hello easy-logger");
334/// }
335pub fn quick_init() -> Result<(), SetLoggerError> {
336    ELogger::new().init()
337}
338///only a init shortcut the usage is the similar with quick_init()
339pub fn init_use_dest(dest: &str) -> Result<(), SetLoggerError> {
340    ELogger::new_dest(dest).init()
341}
342
343#[cfg(test)]
344mod tests {
345    
346}