xref: /xiu/library/logger/src/logger.rs (revision 8bdbb00e)
1 use {
2     super::target::FileTarget,
3     anyhow::Result,
4     chrono::prelude::*,
5     env_logger::{Builder, Env, Target},
6     job_scheduler_ng::{Job, JobScheduler},
7     std::{
8         env, fs,
9         fs::{File, OpenOptions},
10         path::Path,
11         str::FromStr,
12         sync::{
13             mpsc::{channel, Receiver, Sender},
14             Arc, Mutex,
15         },
16         thread,
17         time::Duration,
18     },
19 };
20 
21 #[derive(Clone, Debug, PartialEq)]
22 pub enum Rotate {
23     Day,
24     Hour,
25     Minute,
26 }
27 
28 impl FromStr for Rotate {
29     type Err = ();
from_str(input: &str) -> Result<Rotate, Self::Err>30     fn from_str(input: &str) -> Result<Rotate, Self::Err> {
31         match input {
32             "day" => Ok(Rotate::Day),
33             "hour" => Ok(Rotate::Hour),
34             "minute" => Ok(Rotate::Minute),
35             _ => Err(()),
36         }
37     }
38 }
39 
get_log_file_name(rotate: Rotate) -> String40 fn get_log_file_name(rotate: Rotate) -> String {
41     let local_time: DateTime<Local> = Local::now();
42     match rotate {
43         Rotate::Day => {
44             format!(
45                 "{}{:02}{:02}0000",
46                 local_time.year(),
47                 local_time.month(),
48                 local_time.day(),
49             )
50         }
51         Rotate::Hour => {
52             format!(
53                 "{}{:02}{:02}{:02}00",
54                 local_time.year(),
55                 local_time.month(),
56                 local_time.day(),
57                 local_time.hour(),
58             )
59         }
60         Rotate::Minute => {
61             format!(
62                 "{}{:02}{:02}{:02}{:02}",
63                 local_time.year(),
64                 local_time.month(),
65                 local_time.day(),
66                 local_time.hour(),
67                 local_time.minute()
68             )
69         }
70     }
71 }
72 
gen_log_file(rotate: Rotate, path: String) -> Result<File>73 pub fn gen_log_file(rotate: Rotate, path: String) -> Result<File> {
74     let file_name = get_log_file_name(rotate);
75     let full_path = format!("{path}/{file_name}.log");
76     // println!("file_name: {}", full_path);
77     if !Path::new(&full_path).exists() {
78         //println!("create file : {}", full_path);
79         Ok(File::create(full_path)?)
80     } else {
81         //println!("open file : {}", full_path);
82         let file = OpenOptions::new().append(true).open(full_path)?;
83         Ok(file)
84     }
85 }
86 
gen_log_file_thread_run( file_handler: Arc<Mutex<File>>, rotate: Rotate, path: String, r: Receiver<bool>, )87 pub fn gen_log_file_thread_run(
88     file_handler: Arc<Mutex<File>>,
89     rotate: Rotate,
90     path: String,
91     r: Receiver<bool>,
92 ) {
93     thread::spawn(move || {
94         let mut sched = JobScheduler::new();
95 
96         let scheduler_rule = match rotate {
97             Rotate::Minute => "0 * * * * *",
98             Rotate::Hour => "0 0 * * * *",
99             Rotate::Day => "0 0 0 * * *",
100         };
101 
102         sched.add(Job::new(scheduler_rule.parse().unwrap(), || {
103             let dt: DateTime<Local> = Local::now();
104 
105             let cur_number = format!(
106                 "{}-{:02}-{:02} {:02}:{:02}:00",
107                 dt.year(),
108                 dt.month(),
109                 dt.day(),
110                 dt.hour(),
111                 dt.minute()
112             );
113             println!("time number: {cur_number}");
114 
115             match gen_log_file(rotate.to_owned(), path.to_owned()) {
116                 Ok(file) => {
117                     let mut state = file_handler.lock().expect("Could not lock mutex");
118                     *state = file;
119                 }
120                 Err(err) => {
121                     println!("gen_log_file err : {err}");
122                 }
123             }
124         }));
125         let duration = Duration::from_millis(500);
126         loop {
127             sched.tick();
128             if r.recv_timeout(duration).is_ok() {
129                 return;
130             }
131         }
132     });
133 }
134 #[derive(Default)]
135 pub struct Logger {
136     close_sender: Option<Sender<bool>>,
137 }
138 
139 impl Logger {
new(level: &String, rotate: Option<Rotate>, path: Option<String>) -> Result<Logger>140     pub fn new(level: &String, rotate: Option<Rotate>, path: Option<String>) -> Result<Logger> {
141         if rotate.is_none() || path.is_none() {
142             env::set_var("RUST_LOG", level);
143             env_logger::init();
144             return Ok(Self {
145                 ..Default::default()
146             });
147         }
148 
149         let env = Env::default()
150             .filter_or("MY_LOG_LEVEL", level)
151             // Normally using a pipe as a target would mean a value of false, but this forces it to be true.
152             .write_style_or("MY_LOG_STYLE", "always");
153 
154         let path_val = path.unwrap();
155         let rotate_val = rotate.unwrap();
156 
157         if let Err(err) = fs::create_dir_all(path_val.clone()) {
158             println!("cannot create folder: {path_val}, err: {err}");
159         }
160         let file = gen_log_file(rotate_val.clone(), path_val.clone())?;
161         let target = FileTarget::new(file)?;
162 
163         let handler = target.cur_file_handler.clone();
164         let (send, receiver) = channel::<bool>();
165 
166         gen_log_file_thread_run(handler, rotate_val, path_val, receiver);
167 
168         Builder::from_env(env)
169             .target(Target::Pipe(Box::new(target)))
170             .init();
171 
172         Ok(Self {
173             close_sender: Some(send),
174         })
175     }
stop(&self)176     pub fn stop(&self) {
177         if let Some(sender) = &self.close_sender {
178             if let Err(err) = sender.send(true) {
179                 println!("Logger close err :{err}");
180             }
181         }
182     }
183 }
184 #[cfg(test)]
185 mod tests {
186 
187     use super::Logger;
188     use super::Rotate;
189     use std::fs::OpenOptions;
190     use std::io::Write;
191     use std::time::Duration;
192 
193     #[test]
test_log()194     fn test_log() {
195         let logger = Logger::new(
196             &String::from("info"),
197             Some(Rotate::Minute),
198             Some(String::from("./logs")),
199         )
200         .unwrap();
201 
202         let mut recorder = 0;
203 
204         loop {
205             std::thread::sleep(Duration::from_millis(500));
206             log::trace!("some trace log");
207             log::debug!("some debug log");
208             log::info!("some information log");
209             log::warn!("some warning log");
210             log::error!("some error log");
211             recorder += 1;
212             if recorder > 10 {
213                 logger.stop();
214                 break;
215             }
216         }
217     }
218     #[test]
test_write_file()219     fn test_write_file() {
220         match OpenOptions::new().append(true).open("abc.txt") {
221             Ok(mut file) => {
222                 if let Err(err) = file.write_all(&[b'h', b'e', b'l', b'l', b'o', b'o']) {
223                     println!("file write_all: {err}");
224                 }
225             }
226             Err(err) => {
227                 println!("file create: {err}");
228             }
229         }
230     }
231 }
232