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