1mod writer;
2use std::{path::PathBuf, sync::RwLock};
3
4use crate::writer::BufferedWriter;
5
6use log::{Log, SetLoggerError, LevelFilter};
7use time::{format_description::FormatItem, OffsetDateTime, UtcDateTime};
8
9const TIMESTMAMP_FORMAT: &[FormatItem] = time::macros::format_description!(
10 "[hour]:[minute]:[second]:[subsecond digits:6]"
11);
12
13
14#[derive(PartialEq)]
15enum Timestamps {
16 None,
17 Local,
18 Utc,
19}
20
21pub struct Logger {
22 log_level: LevelFilter,
24 timestamps: Timestamps,
25 thread: bool,
26 target: bool,
27 writers: Vec<RwLock<BufferedWriter>>,
36}
37
38impl Logger {
39
40 #[must_use = "You must call init() to initialize the logger"]
51 pub fn new() -> Logger {
52 Logger {
53 log_level: LevelFilter::Trace ,
54 timestamps: Timestamps::Local,
55 target: false,
56 thread: false,
57 writers: Vec::new() }
58 }
59
60 #[must_use = "You must call init() to initialize the logger"]
62 pub fn with_level(mut self, level: LevelFilter) -> Logger {
63 self.log_level = level;
64 self
65 }
66
67 #[must_use = "You must call init() to initialize the logger"]
69 pub fn with_utc_timestamps(mut self) -> Logger {
70 self.timestamps = Timestamps::Utc;
71 self
72 }
73
74 #[must_use = "You must call init() to initialize the logger"]
76 pub fn with_local_timestamps(mut self) -> Logger {
77 self.timestamps = Timestamps::Local;
78 self
79 }
80
81 #[must_use = "You must call init() to initialize the logger"]
83 pub fn without_timestamps(mut self) -> Logger {
84 self.timestamps = Timestamps::None;
85 self
86 }
87
88 #[must_use = "You must call init() to initialize the logger"]
90 pub fn with_thread(mut self) -> Logger {
91 self.thread = true;
92 self
93 }
94
95 #[must_use = "You must call init() to initialize the logger"]
97 pub fn with_target(mut self) -> Logger {
98 self.target = true;
99 self
100 }
101
102 #[must_use = "You must call init() to initialize the logger"]
104 pub fn without_target(mut self) -> Logger {
105 self.target = false;
106 self
107 }
108
109 #[must_use = "You must call init() to initialize the logger"]
116 pub fn add_writer_stdout(mut self, multi_thread: bool, capacity: Option<usize>) -> Logger {
117 let mut writer = BufferedWriter::new().on_stdout();
118
119 if multi_thread { writer = writer.with_separate_thread(); }
120 if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
121
122 match writer.init() {
123 Ok(initialized_writer) => self.writers.push(RwLock::new(initialized_writer)),
124 Err(error) => println!("Error while initializing writer. Details: {}", error),
125 }
126
127 self
128 }
129
130 #[must_use = "You must call init() to initialize the logger"]
138 pub fn add_writer_file(mut self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>) -> Logger {
139 let mut writer = BufferedWriter::new().on_file(file_path);
140
141 if multi_thread { writer = writer.with_separate_thread(); }
142 if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
143
144 match writer.init() {
145 Ok(initialized_writer) => self.writers.push(RwLock::new(initialized_writer)),
146 Err(error) => println!("Error while initializing writer. Details: {}", error),
147 }
148
149 self
150 }
151
152 pub fn init(self) -> Result<(), SetLoggerError> {
153 log::set_max_level(self.log_level);
154 log::set_boxed_logger(Box::new(self))
155 }
156
157 pub fn log_level(&self) -> LevelFilter {
158 self.log_level
159 }
160}
161
162impl Default for Logger {
163 fn default() -> Self {
164 Logger::new()
165 }
166}
167
168impl Log for Logger {
169 fn enabled(&self, metadata: &log::Metadata) -> bool {
170 metadata.level().to_level_filter() <= self.log_level
171 }
172
173 fn log(&self, record: &log::Record) {
174 if !self.enabled(record.metadata()) {
175 return;
176 }
177
178 let mut target = "";
179
180 if self.target {
181 target = if !record.target().is_empty() {
182 record.target()
183 } else {
184 record.module_path().unwrap_or_default()
185 };
186 }
187
188
189 let thread = if self.thread {
190 format!("{}", std::thread::current().name().unwrap_or("?"))
191 } else {
192 "".to_string()
193 };
194
195 let timestamp = match self.timestamps {
196 Timestamps::None => "".to_string(),
197 Timestamps::Local => format!(
198 "{}",
199 OffsetDateTime::now_local()
200 .expect(concat!(
201 "Could not determine the UTC offset on this system. ",
202 "Consider displaying UTC time instead. ",
203 "Possible causes are that the time crate does not implement \"local_offset_at\" ",
204 "on your system, or that you are running in a multi-threaded environment and ",
205 "the time crate is returning \"None\" from \"local_offset_at\" to avoid unsafe ",
206 "behaviour. See the time crate's documentation for more information. ",
207 "(https://time-rs.github.io/internal-api/time/index.html#feature-flags)"
208 ))
209 .format(TIMESTMAMP_FORMAT)
210 .unwrap()
211 ),
212 Timestamps::Utc => format!( "{}", UtcDateTime::now().format(TIMESTMAMP_FORMAT).unwrap()),
213 };
214
215 let message = format!("{timestamp}-[{target}][{thread}] -> {{{}}} {}", record.level().to_string(), record.args());
216
217 for writer in &self.writers {
218 if let Ok(writer_mut) = writer.write() {
219 writer_mut.write(message.as_str());
220 } else {
221 panic!("Cannot get writer as mutable. RWLock is poisoned!");
222 }
223 }
224 }
225
226 fn flush(&self) {
249 for writer in &self.writers {
250 if let Ok(mut writer_mut) = writer.write() {
251 writer_mut.flush_and_cleanup();
252 } else {
253 panic!("Cannot get writer as mutable. RWLock is poisoned!");
254 }
255 }
256 }
257
258}
259
260#[cfg(test)]
261mod tests {
262 use log::{Metadata, Level};
263
264 use super::*;
265
266 #[test]
267 fn test_default_level() {
268 let builder = Logger::new();
269 assert_eq!(builder.log_level(), LevelFilter::Trace);
270 }
271
272 #[test]
273 fn test_creation_level() {
274 let builder = Logger::new().with_level(LevelFilter::Debug);
275 assert_eq!(builder.log_level(), LevelFilter::Debug);
276 }
277
278 #[test]
279 fn test_logger_enabled() {
280 let logger = Logger::new().with_level(LevelFilter::Debug);
281 assert_eq!(logger.log_level(), LevelFilter::Debug);
282 assert!(logger.enabled(&create_log("test_enabled", Level::Debug)));
283 }
284
285 #[test]
286 fn test_timestamp_default() {
287 let builder = Logger::new();
288 assert!(builder.timestamps == Timestamps::Local);
289 }
290
291 #[test]
292 fn test_utc_timestamp() {
293 let builder = Logger::new().with_utc_timestamps();
294 assert!(builder.timestamps == Timestamps::Utc);
295 }
296
297
298 fn create_log(name: &str, level: Level) -> Metadata {
299 let mut builder = Metadata::builder();
300 builder.level(level);
301 builder.target(name);
302 builder.build()
303 }
304}