1use std::{
32 fs::{create_dir_all, read_dir, remove_file, rename, File, OpenOptions},
33 io::{stdout, Write},
34 path::{Path, PathBuf},
35 process::exit,
36};
37
38use crossbeam_channel::{unbounded, Receiver, Sender};
39use flate2::{write::GzEncoder, Compression};
40use log::{Level, Metadata, Record, SetLoggerError};
41use regex::Regex;
42
43#[cfg(windows)]
44const LINE_ENDING: &'static str = "\r\n";
45#[cfg(not(windows))]
46const LINE_ENDING: &'static str = "\n";
47
48enum Message {
49 Flush,
50 Rotate,
51 Msg(String),
52}
53
54#[derive(Clone)]
55pub struct Logger {
57 log_path: String,
58 retain: usize,
59 buffer_size: usize,
60 rotate_size: usize,
61 stdout: bool,
62 sender: Sender<Message>,
63 receiver: Receiver<Message>,
64}
65
66struct Log {
67 level: Level,
68 sender: Sender<Message>,
69}
70
71static mut TIME_DIFF: i64 = 0;
72
73impl Logger {
74 pub fn init(
91 level: Level,
92 log_path: String,
93 retain: usize,
94 buffer_size: usize,
95 rotate_size: usize,
96 stdout: bool,
97 ) -> Result<Logger, SetLoggerError> {
98 let (sender, receiver) = unbounded();
99 let lf = match level {
100 Level::Trace => log::LevelFilter::Trace,
101 Level::Debug => log::LevelFilter::Debug,
102 Level::Info => log::LevelFilter::Info,
103 Level::Warn => log::LevelFilter::Warn,
104 Level::Error => log::LevelFilter::Error,
105 };
106 log::set_boxed_logger(Box::new(Log {
107 level,
108 sender: sender.clone(),
109 }))
110 .map(|()| log::set_max_level(lf))?;
111 Ok(Logger {
112 log_path,
113 retain,
114 buffer_size,
115 rotate_size,
116 stdout,
117 sender,
118 receiver,
119 })
120 }
121
122 pub fn start(&self) {
124 let this = self.clone();
125 std::thread::spawn(move || {
126 let mut curr_len: usize = 0;
127 let mut bytes_buf = vec![0u8; this.buffer_size];
128 let log_path = Path::new(this.log_path.as_str());
129 let log_dir = log_path.parent().unwrap();
130 let file_stem = log_path.file_stem().unwrap().to_str().unwrap();
131 let file_ext = log_path.extension().unwrap().to_str().unwrap();
132
133 match create_dir_all(&log_dir) {
134 Err(err) => {
135 eprintln!(
136 "buffered_logger: Failed to created dir {} - {}",
137 log_dir.to_str().unwrap(),
138 err
139 );
140 exit(-1);
141 }
142 _ => (),
143 }
144
145 let mut file = OpenOptions::new()
146 .create(true)
147 .append(true)
148 .open(log_path)
149 .unwrap();
150 let mut file_size = file.metadata().unwrap().len() as usize;
151
152 let re = format!(
153 "{}\\.\\d{{6}}\\.\\d{{6}}\\.\\d{{3}}\\.{}\\.gz$",
154 &file_stem, &file_ext
155 );
156 let mut rotated_items: Vec<PathBuf> = read_dir(&log_dir)
157 .unwrap()
158 .filter_map(|res| {
159 let path = res.unwrap().path();
160 let re = Regex::new(&re).unwrap();
161 if re.is_match(path.to_str().unwrap()) {
162 return Some(path);
163 }
164 None
165 })
166 .collect();
167 rotated_items.sort();
168
169 let retain = this.retain;
170 let rotate =
171 |rotated_items: &mut Vec<PathBuf>, file: &mut File, file_size: &mut usize| {
172 *file_size = 0;
173
174 let now = chrono::Local::now().naive_local();
175 let rotated_log_base_name =
176 format!("{}.{}", file_stem, now.format("%y%m%d.%H%M%S%.3f"),);
177 let rotated_log_file_name = format!("{}.{}", rotated_log_base_name, file_ext);
178 let rotated_log_path = log_dir.join(&rotated_log_file_name);
179 rename(log_path, &rotated_log_path).unwrap();
180 *file = OpenOptions::new()
181 .create(true)
182 .append(true)
183 .open(log_path)
184 .unwrap();
185 let zip_name = format!("{}.gz", rotated_log_file_name);
186 let zip_path = log_dir.join(zip_name);
187 rotated_items.push(zip_path.clone());
188 while rotated_items.len() > retain {
189 match remove_file(&rotated_items[0]) {
190 _ => (),
191 }
192 rotated_items.remove(0);
193 }
194 std::thread::spawn(move || {
195 let zip_file = std::fs::File::create(zip_path).unwrap();
196 let mut rotated_file = OpenOptions::new()
197 .read(true)
198 .open(&rotated_log_path)
199 .unwrap();
200 let mut zip = GzEncoder::new(&zip_file, Compression::default());
201 std::io::copy(&mut rotated_file, &mut zip).unwrap();
202 zip.flush().unwrap();
203 zip.finish().unwrap();
204 remove_file(rotated_log_path).unwrap();
205 });
206 };
207
208 loop {
209 let data = this.receiver.recv().unwrap();
210 match data {
211 Message::Flush => {
212 let s = &bytes_buf[0..curr_len];
213 file.write_all(s).unwrap();
214 if this.stdout {
215 stdout().write_all(s).unwrap();
216 }
217 curr_len = 0;
218 }
219 Message::Rotate => {
220 rotate(&mut rotated_items, &mut file, &mut file_size);
221 }
222 Message::Msg(data) => {
223 let len = data.len();
224 let next_len = curr_len + len;
225 let next_file_size = file_size + len;
226 if next_file_size > this.rotate_size {
227 rotate(&mut rotated_items, &mut file, &mut file_size);
228 } else {
229 file_size = next_file_size;
230 }
231 if next_len > this.buffer_size {
232 let s = &bytes_buf[0..curr_len];
233 file.write_all(s).unwrap();
234 if this.stdout {
235 stdout().write_all(s).unwrap();
236 }
237 bytes_buf[0..len].copy_from_slice(data.as_bytes());
238 curr_len = len;
239 } else {
240 bytes_buf[curr_len..next_len].copy_from_slice(data.as_bytes());
241 curr_len = next_len;
242 }
243 }
244 }
245 }
246 });
247 }
248
249 pub fn flush(&self) {
251 self.sender.send(Message::Flush).unwrap();
252 }
253
254 pub fn rotate(&self) {
256 self.sender.send(Message::Rotate).unwrap();
257 }
258
259 pub fn set_time_diff(time_diff: i64) {
261 unsafe {
262 TIME_DIFF = time_diff;
263 }
264 }
265}
266
267impl log::Log for Log {
268 fn enabled(&self, metadata: &Metadata) -> bool {
269 metadata.level() <= self.level
270 }
271
272 fn log(&self, record: &Record) {
273 if self.enabled(record.metadata()) {
274 let now = chrono::Local::now().naive_local();
275 let nsecs = now.timestamp_subsec_nanos();
276 let secs = now.timestamp();
277 let secs = unsafe { secs + TIME_DIFF };
278 let now = chrono::NaiveDateTime::from_timestamp(secs, nsecs);
279 self.sender
280 .send(Message::Msg(format!(
281 "[{} {}] {}{}",
282 now.format("%F %H:%M:%S%.3f"),
283 record.level(),
284 record.args(),
285 LINE_ENDING
286 )))
287 .unwrap();
288 }
289 }
290
291 fn flush(&self) {
292 self.sender.send(Message::Flush).unwrap();
293 }
294}