1mod writer;
2use std::{path::PathBuf, sync::RwLock};
3
4use crate::writer::BufferedWriter;
5
6use log::{LevelFilter, Log, SetLoggerError};
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 writer_levels: Vec<LevelFilter>,
37}
38
39impl Logger {
40
41 #[must_use = "You must call init() to initialize the logger"]
52 pub fn new() -> Logger {
53 Logger {
54 log_level: LevelFilter::Trace ,
55 timestamps: Timestamps::Local,
56 target: false,
57 thread: false,
58 writers: Vec::new(),
59 writer_levels: Vec::new()
60 }
61 }
62
63 #[must_use = "You must call init() to initialize the logger"]
65 pub fn with_level(mut self, level: LevelFilter) -> Logger {
66 self.log_level = level;
67 self
68 }
69
70 #[must_use = "You must call init() to initialize the logger"]
72 pub fn with_utc_timestamps(mut self) -> Logger {
73 self.timestamps = Timestamps::Utc;
74 self
75 }
76
77 #[must_use = "You must call init() to initialize the logger"]
79 pub fn with_local_timestamps(mut self) -> Logger {
80 self.timestamps = Timestamps::Local;
81 self
82 }
83
84 #[must_use = "You must call init() to initialize the logger"]
86 pub fn without_timestamps(mut self) -> Logger {
87 self.timestamps = Timestamps::None;
88 self
89 }
90
91 #[must_use = "You must call init() to initialize the logger"]
93 pub fn with_thread(mut self) -> Logger {
94 self.thread = true;
95 self
96 }
97
98 #[must_use = "You must call init() to initialize the logger"]
100 pub fn with_target(mut self) -> Logger {
101 self.target = true;
102 self
103 }
104
105 #[must_use = "You must call init() to initialize the logger"]
107 pub fn without_target(mut self) -> Logger {
108 self.target = false;
109 self
110 }
111
112 #[must_use = "You must call init() to initialize the logger"]
119 pub fn add_writer_stdout(mut self, multi_thread: bool, capacity: Option<usize>) -> Logger {
120 let default_level = self.log_level.clone();
121 self = self.add_writer_stdout_level(multi_thread, capacity, default_level);
122 self
123 }
124
125 #[must_use = "You must call init() to initialize the logger"]
133 pub fn add_writer_stdout_with_level(mut self, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
134 self = self.add_writer_stdout_level(multi_thread, capacity, level);
135 self
136 }
137
138 #[must_use = "You must call init() to initialize the logger"]
147 pub fn add_writer_file(self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>) -> Logger {
148 let default_level = self.log_level.clone();
149 self.add_writer_file_level(file_path, multi_thread, capacity, default_level)
150 }
151
152 #[must_use = "You must call init() to initialize the logger"]
160 pub fn add_writer_file_with_level(self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
161 self.add_writer_file_level(file_path, multi_thread, capacity, level)
162 }
163
164 pub fn init(self) -> Result<(), SetLoggerError> {
165 log::set_max_level(self.log_level);
166 log::set_boxed_logger(Box::new(self))
167 }
168
169 pub fn log_level(&self) -> LevelFilter {
170 self.log_level
171 }
172
173
174 fn add_writer_stdout_level(mut self, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
175 let mut writer = BufferedWriter::new().on_stdout();
176
177 if multi_thread { writer = writer.with_separate_thread(); }
178 if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
179
180 match writer.init() {
181 Ok(initialized_writer) => {
182 self.writers.push(RwLock::new(initialized_writer));
183 self.writer_levels.push(level.to_owned());
184 },
185 Err(error) => println!("Error while initializing writer. Details: {}", error),
186 }
187
188 self
189 }
190
191
192 fn add_writer_file_level(mut self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
193 let mut writer = BufferedWriter::new().on_file(file_path);
194
195 if multi_thread { writer = writer.with_separate_thread(); }
196 if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
197
198 match writer.init() {
199 Ok(initialized_writer) => {
200 self.writers.push(RwLock::new(initialized_writer));
201 self.writer_levels.push(level.to_owned());
202 },
203 Err(error) => println!("Error while initializing writer. Details: {}", error),
204 }
205
206 self
207 }
208}
209
210impl Default for Logger {
211 fn default() -> Self {
212 Logger::new()
213 }
214}
215
216impl Log for Logger {
217 fn enabled(&self, metadata: &log::Metadata) -> bool {
218 metadata.level().to_level_filter() <= self.log_level
219 }
220
221 fn log(&self, record: &log::Record) {
222 if !self.enabled(record.metadata()) {
223 return;
224 }
225
226 let mut target = "";
227
228 if self.target {
229 target = if !record.target().is_empty() {
230 record.target()
231 } else {
232 record.module_path().unwrap_or_default()
233 };
234 }
235
236
237 let thread = if self.thread {
238 if let Some(thread_name) = std::thread::current().name() {
239 format!("{}", thread_name)
240 } else {
241 format!("{:?}", std::thread::current().id())
242 }
243 } else {
244 "".to_string()
245 };
246
247 let timestamp = match self.timestamps {
248 Timestamps::None => "".to_string(),
249 Timestamps::Local => format!(
250 "{}",
251 OffsetDateTime::now_local()
252 .expect(concat!(
253 "Could not determine the UTC offset on this system. ",
254 "Consider displaying UTC time instead. ",
255 "Possible causes are that the time crate does not implement \"local_offset_at\" ",
256 "on your system, or that you are running in a multi-threaded environment and ",
257 "the time crate is returning \"None\" from \"local_offset_at\" to avoid unsafe ",
258 "behaviour. See the time crate's documentation for more information. ",
259 "(https://time-rs.github.io/internal-api/time/index.html#feature-flags)"
260 ))
261 .format(TIMESTMAMP_FORMAT)
262 .unwrap()
263 ),
264 Timestamps::Utc => format!( "{}", UtcDateTime::now().format(TIMESTMAMP_FORMAT).unwrap()),
265 };
266
267 let message = format!("{timestamp}-[{target}][{thread}] -> {{{}}} {}", record.level().to_string(), record.args());
268
269 for (index, writer) in self.writers.iter().enumerate() {
270 if index >= self.writer_levels.len() {
271 panic!("Level index out of range!");
272 }
273
274 if record.metadata().level().to_level_filter() > self.writer_levels[index] {
276 continue;
277 }
278
279 if let Ok(writer_mut) = writer.write() {
280 writer_mut.write(message.as_str());
281 } else {
282 panic!("Cannot get writer as mutable. RWLock is poisoned!");
283 }
284 }
285 }
286
287 fn flush(&self) {
310 for writer in &self.writers {
311 if let Ok(mut writer_mut) = writer.write() {
312 writer_mut.flush_and_cleanup();
313 } else {
314 panic!("Cannot get writer as mutable. RWLock is poisoned!");
315 }
316 }
317 }
318
319}
320
321#[cfg(test)]
322mod tests {
323 use log::{Metadata, Level};
324
325 use super::*;
326
327 #[test]
328 fn test_default_level() {
329 let builder = Logger::new();
330 assert_eq!(builder.log_level(), LevelFilter::Trace);
331 }
332
333 #[test]
334 fn test_creation_level() {
335 let builder = Logger::new().with_level(LevelFilter::Debug);
336 assert_eq!(builder.log_level(), LevelFilter::Debug);
337 }
338
339 #[test]
340 fn test_logger_enabled() {
341 let logger = Logger::new().with_level(LevelFilter::Debug);
342 assert_eq!(logger.log_level(), LevelFilter::Debug);
343 assert!(logger.enabled(&create_log("test_enabled", Level::Debug)));
344 }
345
346 #[test]
347 fn test_timestamp_default() {
348 let builder = Logger::new();
349 assert!(builder.timestamps == Timestamps::Local);
350 }
351
352 #[test]
353 fn test_utc_timestamp() {
354 let builder = Logger::new().with_utc_timestamps();
355 assert!(builder.timestamps == Timestamps::Utc);
356 }
357
358
359 fn create_log(name: &str, level: Level) -> Metadata {
360 let mut builder = Metadata::builder();
361 builder.level(level);
362 builder.target(name);
363 builder.build()
364 }
365}