r3bl_simple_logger/loggers/
testlog.rs

1/*
2 *   Copyright (c) 2023 R3BL LLC
3 *   All rights reserved.
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *   http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18//! Module providing the TestLogger Implementation
19
20use std::thread;
21
22use log::{set_boxed_logger, set_max_level, LevelFilter, Log, Metadata, Record, SetLoggerError};
23
24use super::logging::should_skip;
25use crate::{config::TimeFormat, Config, LevelPadding, SharedLogger};
26
27/// The TestLogger struct. Provides a very basic Logger implementation that may be captured by cargo.
28pub struct TestLogger {
29    level: LevelFilter,
30    config: Config,
31}
32
33impl TestLogger {
34    /// init function. Globally initializes the TestLogger as the one and only used log facility.
35    ///
36    /// Takes the desired `Level` and `Config` as arguments. They cannot be changed later on.
37    /// Fails if another Logger was already initialized.
38    ///
39    /// # Examples
40    /// ```
41    /// # extern crate r3bl_simple_logger;
42    /// # use r3bl_simple_logger::*;
43    /// # fn main() {
44    /// // another logger
45    /// # let _ = TestLogger::init(LevelFilter::Info, Config::default());
46    /// let _ = TestLogger::init(LevelFilter::Info, Config::default());
47    /// # }
48    /// ```
49    pub fn init(log_level: LevelFilter, config: Config) -> Result<(), SetLoggerError> {
50        set_max_level(log_level);
51        set_boxed_logger(TestLogger::new(log_level, config))
52    }
53
54    /// allows to create a new logger, that can be independently used, no matter what is globally set.
55    ///
56    /// no macros are provided for this case and you probably
57    /// dont want to use this function, but `init()`, if you dont want to build a `CombinedLogger`.
58    ///
59    /// Takes the desired `Level` and `Config` as arguments. They cannot be changed later on.
60    ///
61    /// # Examples
62    /// ```
63    /// # extern crate r3bl_simple_logger;
64    /// # use r3bl_simple_logger::*;
65    /// # fn main() {
66    /// // another logger
67    /// # let test_logger = TestLogger::new(LevelFilter::Info, Config::default());
68    /// let test_logger = TestLogger::new(LevelFilter::Info, Config::default());
69    /// # }
70    /// ```
71    #[must_use]
72    pub fn new(log_level: LevelFilter, config: Config) -> Box<TestLogger> {
73        Box::new(TestLogger {
74            level: log_level,
75            config,
76        })
77    }
78}
79
80impl Log for TestLogger {
81    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
82        metadata.level() <= self.level
83    }
84
85    fn log(&self, record: &Record<'_>) {
86        if self.enabled(record.metadata()) {
87            log(&self.config, record);
88        }
89    }
90
91    fn flush(&self) {}
92}
93
94impl SharedLogger for TestLogger {
95    fn level(&self) -> LevelFilter {
96        self.level
97    }
98
99    fn config(&self) -> Option<&Config> {
100        Some(&self.config)
101    }
102
103    fn as_log(self: Box<Self>) -> Box<dyn Log> {
104        Box::new(*self)
105    }
106}
107
108#[inline(always)]
109pub fn log(config: &Config, record: &Record<'_>) {
110    if should_skip(config, record) {
111        return;
112    }
113
114    if config.time <= record.level() && config.time != LevelFilter::Off {
115        write_time(config);
116    }
117
118    if config.level <= record.level() && config.level != LevelFilter::Off {
119        write_level(record, config);
120    }
121
122    if config.thread < record.level() && config.thread != LevelFilter::Off {
123        write_thread_id();
124    }
125
126    if config.target <= record.level() && config.target != LevelFilter::Off {
127        write_target(record);
128    }
129
130    if config.location <= record.level() && config.location != LevelFilter::Off {
131        write_location(record);
132    }
133
134    if config.module <= record.level() && config.module != LevelFilter::Off {
135        write_module(record);
136    }
137
138    write_args(record);
139}
140
141#[inline(always)]
142pub fn write_time(config: &Config) {
143    use time::format_description::well_known::*;
144
145    let time = time::OffsetDateTime::now_utc().to_offset(config.time_offset);
146    let res = match config.time_format {
147        TimeFormat::Rfc2822 => time.format(&Rfc2822),
148        TimeFormat::Rfc3339 => time.format(&Rfc3339),
149        TimeFormat::Custom(format) => time.format(&format),
150    };
151    match res {
152        Ok(time) => print!("{} ", time),
153        Err(err) => panic!("Invalid time format: {}", err),
154    };
155}
156
157#[inline(always)]
158pub fn write_level(record: &Record<'_>, config: &Config) {
159    match config.level_padding {
160        LevelPadding::Left => print!("[{: >5}] ", record.level()),
161        LevelPadding::Right => print!("[{: <5}] ", record.level()),
162        LevelPadding::Off => print!("[{}] ", record.level()),
163    };
164}
165
166#[inline(always)]
167pub fn write_thread_id() {
168    let id = format!("{:?}", thread::current().id());
169    let id = id.replace("ThreadId(", "");
170    let id = id.replace(')', "");
171    print!("({}) ", id);
172}
173
174#[inline(always)]
175pub fn write_target(record: &Record<'_>) {
176    print!("{}: ", record.target());
177}
178
179#[inline(always)]
180pub fn write_location(record: &Record<'_>) {
181    let file = record.file().unwrap_or("<unknown>");
182    if let Some(line) = record.line() {
183        print!("[{}:{}] ", file, line);
184    } else {
185        print!("[{}:<unknown>] ", file);
186    }
187}
188
189#[inline(always)]
190pub fn write_module(record: &Record<'_>) {
191    let module = record.module_path().unwrap_or("<unknown>");
192    print!("[{}] ", module);
193}
194
195#[inline(always)]
196pub fn write_args(record: &Record<'_>) {
197    println!("{}", record.args());
198}