layla_log/
logger.rs

1use super::{msg::LogMessage, position, LogLevel, Setting};
2use chrono::FixedOffset;
3#[cfg(not(feature = "async"))]
4use std::fs::{self, File};
5#[cfg(not(feature = "async"))]
6use std::io::Write;
7#[cfg(feature = "async")]
8use tokio::fs::{self, File};
9#[cfg(feature = "async")]
10use tokio::io::AsyncWriteExt;
11
12/// A writer for buffering the log and writing them into the suitable files.
13#[derive(Debug)]
14pub struct Logger {
15    /// the file that is currently being written.
16    file: Option<File>,
17    /// the current index of the file.
18    current_index: usize,
19    /// the length of the log that have been written.
20    used_length: usize,
21    /// a buffer to store the prefex of log files' name.
22    current_file_prefix: String,
23    /// check if the writer is initialized.
24    init: bool,
25    /// setting of the logger.
26    setting: Setting,
27}
28
29impl Logger {
30    /// Initialize the logger with all default setting.
31    pub(crate) fn new() -> Self {
32        let setting = Setting {
33            ..Default::default()
34        };
35
36        let mut buffer = Self {
37            file: None,
38            current_index: 0,
39            used_length: 0,
40            init: false,
41            current_file_prefix: format!(
42                "{}",
43                chrono::Utc::now()
44                    .with_timezone(&FixedOffset::east_opt(setting.time_zone * 3600).unwrap())
45                    .format(&setting.file_time_format)
46            ),
47            setting,
48        };
49        buffer.check_dir();
50        buffer.current_index = buffer.get_index_not_async(&buffer.current_file_prefix);
51        buffer
52    }
53
54    /// Get the path of the log file.
55    fn get_path(&self, time_prefix: &str, index: usize) -> String {
56        format!("{}/{}_{}.log", self.setting.dir_path, time_prefix, index)
57    }
58
59    /// Disable the logger.
60    pub(crate) fn disable(&mut self) {
61        self.setting.disabled = true;
62    }
63
64    /// Enable the logger.
65    pub(crate) fn enable(&mut self) {
66        self.setting.disabled = false;
67    }
68
69    /// Get the index of the current log file.
70    /// This is used when resume the logging, since have to keep a continuos order of the log files.
71    fn get_index_not_async(&self, time_prefix: &str) -> usize {
72        let mut count = 0;
73        loop {
74            let path = self.get_path(time_prefix, count);
75            // if the file exists, then the index is the next one
76            if let Ok(_) = std::fs::File::open(path) {
77                count += 1
78            } else {
79                return count;
80            }
81        }
82    }
83
84    /// check the dir if it exists. if not, create it
85    fn check_dir(&self) {
86        if !std::path::Path::new(&self.setting.dir_path).exists() {
87            std::fs::create_dir(&self.setting.dir_path).expect("Failed to create directory");
88        }
89    }
90}
91
92#[cfg(feature = "async")]
93impl Logger {
94    /// Customize and initialize the log writer.
95    pub(crate) async fn init(&mut self, setting: Setting) {
96        if self.init {
97            let position = position!().to_string();
98            self.warn("Log writer had been initialized!", position)
99                .await;
100            return;
101        }
102
103        self.file = None;
104        self.used_length = 0;
105
106        self.setting = setting;
107
108        self.init = true;
109        self.current_file_prefix = format!(
110            "{}",
111            chrono::Utc::now()
112                .with_timezone(&FixedOffset::east_opt(self.setting.time_zone * 3600).unwrap())
113                .format("%Y-%m-%d")
114        );
115        self.current_index = self.get_index(&self.current_file_prefix).await;
116    }
117
118    /// clear the log directory. (remove all the log files in the directory)
119    pub(crate) async fn clear_dir(&mut self) {
120        fs::remove_dir_all(&self.setting.dir_path)
121            .await
122            .expect("Cannot remove the dir.");
123        fs::create_dir(&self.setting.dir_path)
124            .await
125            .expect("Cannot create the dir.");
126        self.current_index = 0;
127        self.used_length = 0;
128        self.file = None;
129    }
130
131    /// Write a single log message to the file.
132    async fn write(&mut self, msg: &LogMessage) {
133        // if the logger is disabled, return directly
134        if self.setting.disabled {
135            return;
136        }
137
138        for i in msg.split_enter() {
139            if self.file.is_none() {
140                self.file = Some(self.get_file().await);
141            }
142
143            // check if the time prefix has changed
144            // (when a new day begins)
145            let time_prefix = format!(
146                "{}",
147                chrono::Utc::now()
148                    .with_timezone(&FixedOffset::east_opt(self.setting.time_zone * 3600).unwrap())
149                    .format("%Y-%m-%d")
150            );
151            if self.current_file_prefix != time_prefix {
152                self.current_file_prefix = time_prefix;
153                self.current_index = self.get_index(&self.current_file_prefix).await;
154                self.used_length = 0;
155                self.file = Some(self.get_file().await);
156            };
157
158            // check if should print to terminal.
159            // requirement: print out is enabled and the level is high enough
160            if self.setting.print_out
161                && self.setting.terminal_print_level.get_level() <= i.get_level()
162            {
163                println!("{}", i.print())
164            };
165
166            // check if should write to file.
167            // requirement: the level is high enough
168            if self.setting.file_record_level.get_level() <= i.get_level() {
169                self.file
170                    .as_mut()
171                    .unwrap()
172                    .write_all((i.print() + "\n").as_bytes())
173                    .await
174                    .expect("Cannot write into the log file.");
175                self.used_length += 1;
176            };
177        }
178
179        // check if the file is full or unlimited size
180        if self.setting.single_length != 0 && self.used_length >= self.setting.single_length {
181            self.current_index += 1;
182            self.used_length = 0;
183            self.file = None;
184        }
185    }
186
187    /// provide a method to log something by only a given string and [`LogLevel`].
188    pub async fn record(&mut self, log_level: LogLevel, message: &str, position: String) {
189        if !self.init {
190            self.init = true
191        }
192        let mut msg = LogMessage::new(
193            log_level,
194            message.to_string(),
195            self.setting.time_zone,
196            position,
197        );
198        msg.time.detailed_display = self.setting.time_detailed_display;
199        self.write(&msg).await;
200    }
201
202    /// Record an info log.
203    pub async fn info(&mut self, message: &str, position: String) {
204        self.record(LogLevel::Info, message, position).await;
205    }
206
207    /// Record a debug log.
208    pub async fn debug(&mut self, message: &str, position: String) {
209        self.record(LogLevel::Debug, message, position).await;
210    }
211
212    /// Record a warn log.
213    pub async fn warn(&mut self, message: &str, position: String) {
214        self.record(LogLevel::Warn, message, position).await;
215    }
216
217    /// Record an error log.
218    pub async fn error(&mut self, message: &str, position: String) {
219        self.record(LogLevel::Error, message, position).await;
220    }
221
222    /// Record a trace log.
223    pub async fn trace(&mut self, message: &str, position: String) {
224        self.record(LogLevel::Trace, message, position).await;
225    }
226
227    /// Get the file object of the log file.
228    async fn get_file(&self) -> File {
229        let path = self.get_path(&self.current_file_prefix, self.current_index);
230        // enable read and write and create a new file if not exist
231        File::options()
232            .read(true)
233            .write(true)
234            .create_new(true)
235            .open(path)
236            .await
237            .expect("Cannot create the log file.")
238    }
239
240    /// Get the index of the current log file.
241    /// This is used when resume the logging, since have to keep a continuos order of the log files.
242    async fn get_index(&self, time_prefix: &str) -> usize {
243        let mut count = 0;
244        loop {
245            let path = self.get_path(time_prefix, count);
246            // if the file exists, then the index is the next one
247            if let Ok(_) = File::open(path).await {
248                count += 1
249            } else {
250                return count;
251            }
252        }
253    }
254}
255
256#[cfg(not(feature = "async"))]
257impl Logger {
258    /// Customize and initialize the log writer.
259    pub(crate) fn init(&mut self, setting: Setting) {
260        if self.init {
261            let position = position!().to_string();
262            self.warn("Log writer had been initialized!", position);
263            return;
264        }
265
266        self.file = None;
267        self.used_length = 0;
268
269        self.setting = setting;
270
271        self.init = true;
272        self.current_file_prefix = format!(
273            "{}",
274            chrono::Utc::now()
275                .with_timezone(&FixedOffset::east_opt(self.setting.time_zone * 3600).unwrap())
276                .format("%Y-%m-%d")
277        );
278        self.current_index = self.get_index(&self.current_file_prefix);
279    }
280
281    /// clear the log directory.
282    pub(crate) fn clear_dir(&mut self) {
283        fs::remove_dir_all(&self.setting.dir_path).expect("Cannot remove the dir.");
284        fs::create_dir(&self.setting.dir_path).expect("Cannot create the dir.");
285        self.current_index = 0;
286        self.used_length = 0;
287        self.file = None;
288        self.current_file_prefix = format!(
289            "{}",
290            chrono::Utc::now()
291                .with_timezone(&FixedOffset::east_opt(self.setting.time_zone * 3600).unwrap())
292                .format(&self.setting.file_time_format)
293        );
294    }
295
296    /// Write a single log message to the file.
297    fn write(&mut self, msg: &LogMessage) {
298        if self.setting.disabled {
299            return;
300        }
301
302        if self.file.is_none() {
303            self.file = Some(self.get_file());
304        }
305
306        for i in msg.split_enter() {
307            // check if the time prefix has changed
308            // (when a new day begins)
309            let time_prefix = format!(
310                "{}",
311                chrono::Utc::now()
312                    .with_timezone(&FixedOffset::east_opt(self.setting.time_zone * 3600).unwrap())
313                    .format("%Y-%m-%d")
314            );
315            if self.current_file_prefix != time_prefix {
316                self.current_file_prefix = time_prefix;
317                self.current_index = self.get_index(&self.current_file_prefix);
318                self.used_length = 0;
319                self.file = Some(self.get_file());
320            };
321
322            // check if should print to terminal.
323            // requirement: print out is enabled and the level is high enough
324            if self.setting.print_out
325                && self.setting.terminal_print_level.get_level() <= i.get_level()
326            {
327                println!("{}", i.print())
328            };
329
330            // check if should write to file.
331            // requirement: the level is high enough
332            if self.setting.file_record_level.get_level() <= i.get_level() {
333                self.file
334                    .as_mut()
335                    .unwrap()
336                    .write_all((i.print() + "\n").as_bytes())
337                    .expect("Cannot write into the log file.");
338                self.used_length += 1;
339            };
340        }
341
342        if self.setting.single_length != 0 && self.used_length >= self.setting.single_length {
343            self.current_index += 1;
344            self.used_length = 0;
345            self.file = None;
346        }
347    }
348
349    /// provide a method to log something by only a given string and [`LogLevel`].
350    pub fn record(&mut self, log_level: LogLevel, message: &str, position: String) {
351        if !self.init {
352            self.init = true
353        }
354        let mut msg = LogMessage::new(
355            log_level,
356            message.to_string(),
357            self.setting.time_zone,
358            position,
359        );
360        msg.time.detailed_display = self.setting.time_detailed_display;
361        self.write(&msg);
362    }
363
364    /// Record an info log.
365    pub fn info(&mut self, message: &str, position: String) {
366        self.record(LogLevel::Info, message, position);
367    }
368
369    /// Record a debug log.
370    pub fn debug(&mut self, message: &str, position: String) {
371        self.record(LogLevel::Debug, message, position);
372    }
373
374    /// Record a warn log.
375    pub fn warn(&mut self, message: &str, position: String) {
376        self.record(LogLevel::Warn, message, position);
377    }
378
379    /// Record an error log.
380    pub fn error(&mut self, message: &str, position: String) {
381        self.record(LogLevel::Error, message, position);
382    }
383
384    /// Record a trace log.
385    pub fn trace(&mut self, message: &str, position: String) {
386        self.record(LogLevel::Trace, message, position);
387    }
388
389    /// Get the index of the current log file.
390    /// This is used when resume the logging, since have to keep a continuos order of the log files.
391    fn get_index(&self, time_prefix: &str) -> usize {
392        let mut count = 0;
393        loop {
394            let path = self.get_path(time_prefix, count);
395            // if the file exists, then the index is the next one
396            if let Ok(_) = File::open(path) {
397                count += 1
398            } else {
399                return count;
400            }
401        }
402    }
403
404    /// Get the file object of the log file.
405    fn get_file(&self) -> File {
406        let path = self.get_path(&self.current_file_prefix, self.current_index);
407        // enable read and write and create a new file if not exist
408        File::options()
409            .read(true)
410            .write(true)
411            .create_new(true)
412            .open(path)
413            .expect("Cannot create the log file.")
414    }
415}
416
417unsafe impl Send for Logger {}