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}