bp3d_logger/
lib.rs

1// Copyright (c) 2021, BlockProject 3D
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above copyright notice,
11//       this list of conditions and the following disclaimer in the documentation
12//       and/or other materials provided with the distribution.
13//     * Neither the name of BlockProject 3D nor the names of its contributors
14//       may be used to endorse or promote products derived from this software
15//       without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29// The reason why this is needed is because the 3 examples of usage of the Logger struct requires
30// some context to not make it confusing.
31#![allow(clippy::needless_doctest_main)]
32
33mod backend;
34mod internal;
35mod easy_termcolor;
36
37use bp3d_fs::dirs::App;
38use crossbeam_channel::Receiver;
39use log::{Level, Log};
40use once_cell::sync::Lazy;
41use std::path::PathBuf;
42use std::sync::atomic::Ordering;
43use crate::backend::ENABLE_STDOUT;
44
45/// Represents a log message in the [LogBuffer](crate::LogBuffer).
46#[derive(Clone)]
47pub struct LogMsg {
48    /// The message string.
49    pub msg: String,
50
51    /// The crate name that issued this log.
52    pub target: String,
53
54    /// The log level.
55    pub level: Level,
56}
57
58/// The log buffer type.
59pub type LogBuffer = Receiver<LogMsg>;
60
61/// Trait to allow getting a log directory from either a bp3d_fs::dirs::App or a String.
62pub trait GetLogs {
63    /// Gets the log directory as a PathBuf.
64    ///
65    /// Returns None if no directory could be computed.
66    fn get_logs(self) -> Option<PathBuf>;
67}
68
69impl<'a> GetLogs for &'a String {
70    fn get_logs(self) -> Option<PathBuf> {
71        self.as_str().get_logs()
72    }
73}
74
75impl<'a, 'b> GetLogs for &'a App<'b> {
76    fn get_logs(self) -> Option<PathBuf> {
77        self.get_logs().map(|v| v.into()).ok()
78    }
79}
80
81impl<'a> GetLogs for &'a str {
82    fn get_logs(self) -> Option<PathBuf> {
83        let app = App::new(self);
84        app.get_logs().map(|v| v.into()).ok()
85    }
86}
87
88/// Enum of the different color settings when printing to stdout/stderr.
89#[derive(Debug, Copy, Clone)]
90pub enum Colors {
91    /// Color printing is always enabled.
92    Enabled,
93
94    /// Color printing is always disabled.
95    Disabled,
96
97    /// Color printing is automatic (if current terminal is a tty, print with colors, otherwise
98    /// print without colors).
99    Auto
100}
101
102impl Default for Colors {
103    fn default() -> Self {
104        Self::Disabled
105    }
106}
107
108/// The base logger builder/initializer.
109///
110/// # Examples
111///
112/// The following example shows basic initialization of this logger.
113/// ```
114/// use bp3d_logger::Logger;
115/// use log::info;
116/// use log::LevelFilter;
117///
118/// fn main() {
119///     let _guard = Logger::new().add_stdout().add_file("my-app").start();
120///     log::set_max_level(LevelFilter::Info);
121///     //...
122///     info!("Example message");
123/// }
124/// ```
125///
126/// The following example shows initialization of this logger with a return value.
127/// ```
128/// use bp3d_logger::Logger;
129/// use bp3d_logger::with_logger;
130/// use log::info;
131/// use log::LevelFilter;
132///
133/// fn main() {
134///     let code = with_logger(Logger::new().add_stdout().add_file("my-app"), || {
135///         log::set_max_level(LevelFilter::Info);
136///         //...
137///         info!("Example message");
138///         0
139///     });
140///     std::process::exit(code);
141/// }
142/// ```
143///
144/// The following example shows initialization of this logger and use of the log buffer.
145/// ```
146/// use bp3d_logger::Logger;
147/// use log::info;
148/// use log::LevelFilter;
149///
150/// fn main() {
151///     let _guard = Logger::new().add_stdout().add_file("my-app").start();
152///     log::set_max_level(LevelFilter::Info);
153///     bp3d_logger::enable_log_buffer(); // Enable log redirect pump into application channel.
154///     //... application code with log redirect pump.
155///     info!("Example message");
156///     let l = bp3d_logger::get_log_buffer().recv().unwrap();// Capture the last log message.
157///     println!("Last log message: {}", l.msg);
158///     bp3d_logger::disable_log_buffer();
159///     //... application code without log redirect pump.
160/// }
161/// ```
162pub struct Logger {
163    colors: Colors,
164    smart_stderr: bool,
165    std: Option<backend::StdBackend>,
166    file: Option<backend::FileBackend>,
167}
168
169impl Default for Logger {
170    fn default() -> Self {
171        Self {
172            colors: Colors::default(),
173            smart_stderr: true,
174            std: None,
175            file: None
176        }
177    }
178}
179
180impl Logger {
181    /// Creates a new instance of a logger builder.
182    pub fn new() -> Logger {
183        Logger::default()
184    }
185
186    /// Sets the colors state when logging to stdout/stderr.
187    ///
188    /// The default behavior is to disable colors.
189    pub fn colors(mut self, state: Colors) -> Self {
190        self.colors = state;
191        self
192    }
193
194    /// Enables or disables automatic redirection of error logs to stderr.
195    ///
196    /// The default for this flag is true.
197    pub fn smart_stderr(mut self, flag: bool) -> Self {
198        self.smart_stderr = flag;
199        self
200    }
201
202    /// Enables stdout logging.
203    pub fn add_stdout(mut self) -> Self {
204        self.std = Some(backend::StdBackend::new(self.smart_stderr, self.colors));
205        self
206    }
207
208    /// Enables file logging to the given application.
209    ///
210    /// The application is given as a reference to [GetLogs](crate::GetLogs) to allow obtaining
211    /// a log directory from various sources.
212    ///
213    /// If the log directory could not be found the function prints an error to stderr.
214    pub fn add_file<T: GetLogs>(mut self, app: T) -> Self {
215        if let Some(logs) = app.get_logs() {
216            self.file = Some(backend::FileBackend::new(logs));
217        } else {
218            eprintln!("Failed to obtain application log directory");
219        }
220        self
221    }
222
223    /// Initializes the log implementation with this current configuration.
224    ///
225    /// NOTE: This returns a guard to flush all log buffers before returning. It is
226    /// necessary to flush log buffers because this implementation uses threads
227    /// to avoid blocking the main thread when issuing logs.
228    ///
229    /// NOTE 2: There are no safety concerns with running twice this function in the same
230    /// application, only that calling this function may be slow due to thread management.
231    pub fn start(self) -> Guard {
232        let _ = log::set_logger(&*BP3D_LOGGER); // Ignore the error
233        // (we can't do anything if there's already a logger set;
234        // unfortunately that is a limitation of the log crate)
235
236        BP3D_LOGGER.start_new_thread(self); // Re-start the logging thread with the new configuration.
237        BP3D_LOGGER.enable(true); // Enable logging.
238        Guard
239    }
240
241    /// Initializes the log implementation with this current configuration.
242    ///
243    /// NOTE: Since version 1.1.0 this is a redirect to bp3d_logger::with_logger.
244    #[deprecated(since = "1.1.0", note = "please use bp3d_logger::with_logger")]
245    pub fn run<R, F: FnOnce() -> R>(self, f: F) -> R {
246        with_logger(self, f)
247    }
248}
249
250/// Represents a logger guard.
251///
252/// WARNING: Once this guard is dropped messages are no longer captured.
253pub struct Guard;
254
255impl Drop for Guard {
256    fn drop(&mut self) {
257        // Disable the logger so further log requests are dropped.
258        BP3D_LOGGER.enable(false);
259        // Send termination command and join with logging thread.
260        BP3D_LOGGER.terminate();
261        // Disable log buffer.
262        BP3D_LOGGER.enable_log_buffer(false);
263        // Clear by force all content of in memory log buffer.
264        BP3D_LOGGER.clear_log_buffer();
265    }
266}
267
268static BP3D_LOGGER: Lazy<internal::LoggerImpl> = Lazy::new(internal::LoggerImpl::new);
269
270/// Enables the log redirect pump.
271pub fn enable_log_buffer() {
272    BP3D_LOGGER.enable_log_buffer(true);
273}
274
275/// Disables the log redirect pump.
276pub fn disable_log_buffer() {
277    BP3D_LOGGER.enable_log_buffer(false);
278    BP3D_LOGGER.clear_log_buffer();
279}
280
281/// Enables the stdout/stderr logger.
282pub fn enable_stdout() {
283    ENABLE_STDOUT.store(true, Ordering::Release);
284}
285
286/// Disables the stdout/stderr logger.
287pub fn disable_stdout() {
288    ENABLE_STDOUT.store(false, Ordering::Release);
289}
290
291/// Returns the buffer from the log redirect pump.
292pub fn get_log_buffer() -> LogBuffer {
293    BP3D_LOGGER.get_log_buffer()
294}
295
296/// Low-level log function. This injects log messages directly into the logging thread channel.
297///
298/// This function applies basic formatting depending on the backend:
299/// - For stdout/stderr backend the format is <target> \[level\] msg
300/// - For file backend the format is \[level\] msg and the message is recorded in the file
301/// corresponding to the log target.
302pub fn raw_log(msg: LogMsg) {
303    BP3D_LOGGER.low_level_log(msg)
304}
305
306/// Shortcut to the flush command to avoid having to call behind the dyn interface.
307pub fn flush() {
308    BP3D_LOGGER.flush();
309}
310
311/// Returns true if the logger is currently enabled and is capturing log messages.
312pub fn enabled() -> bool {
313    BP3D_LOGGER.is_enabled()
314}
315
316/// Runs a closure in scope of a logger configuration, then free the given logger configuration
317/// and return closure result.
318pub fn with_logger<R, F: FnOnce() -> R>(logger: Logger, f: F) -> R {
319    let _guard = logger.start();
320    f()
321}