indicatif_log_bridge/
lib.rs

1//! Tired of your log lines and progress bars mixing up? indicatif_log_bridge to the rescue!
2//!
3//! Simply wrap your favourite logging implementation in [LogWrapper]
4//!     and those worries are a thing of the past.
5//!
6//! Just remember add each [ProgressBar](indicatif::ProgressBar) to the [MultiProgress] you used
7//!     , otherwise you are back to ghostly halves of progress bars everywhere.
8//!
9//! # Example
10//! ```rust
11//!     # use indicatif_log_bridge::LogWrapper;
12//!     # use log::info;
13//!     # use indicatif::{MultiProgress, ProgressBar};
14//!     # use std::time::Duration;
15//!     let logger =
16//!         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
17//!             .build();
18//!     let level = logger.filter();
19//!     let multi = MultiProgress::new();
20//!
21//!     LogWrapper::new(multi.clone(), logger)
22//!         .try_init()
23//!         .unwrap();
24//!     log::set_max_level(level);
25//!
26//!     let pg = multi.add(ProgressBar::new(10));
27//!     for i in (0..10) {
28//!         std::thread::sleep(Duration::from_micros(100));
29//!         info!("iteration {}", i);
30//!         pg.inc(1);
31//!     }
32//!     pg.finish();
33//!     multi.remove(&pg);
34//! ```
35//! The code of this crate is pretty simple, so feel free to check it out.
36//!
37//!
38//! # Known Issues
39//! ## Wrong Global Log Level
40//! The log framework has a global minimum level, set using [log::set_max_level].
41//! If that is set to Debug, the trace! macro will not fire at all.
42//! The [Log] trait does not provide a standartized way of querying the expected level.
43//! [LogWrapper::try_init] tries hard to find the correct level, but does not always get it right,
44//!     especially if different levels are specified for different modules or crates,
45//!         as is often the case with the `env_logger` crate.
46//!
47//! ### Workaround
48//! For `env_logger` specifically you can use `logger.filter()` to query the level
49//! before constructing and initializing the [LogWrapper] and then passit to [log::set_max_level]
50//! afterwards.
51//! If you copy the [example code](#example) you should be fine.
52
53use indicatif::MultiProgress;
54use log::Log;
55
56/// Wraps a MultiProgress and a Log implementor,
57/// calling .suspend on the MultiProgress while writing the log message,
58/// thereby preventing progress bars and logs from getting mixed up.
59///
60/// You simply have to add every ProgressBar to the passed MultiProgress.
61pub struct LogWrapper<L: Log> {
62    bar: MultiProgress,
63    log: L,
64}
65
66impl<L: Log + 'static> LogWrapper<L> {
67    pub fn new(bar: MultiProgress, log: L) -> Self {
68        Self { bar, log }
69    }
70
71    /// Installs this as the global logger.
72    ///
73    /// Tries to find the correct argument to log::set_max_level
74    /// by reading the logger configuration,
75    /// you may want to set it manually though.
76    /// For more details read the [known issues](index.html#wrong-global-log-level).
77    pub fn try_init(self) -> Result<(), log::SetLoggerError> {
78        use log::LevelFilter::*;
79        let levels = [Off, Error, Warn, Info, Debug, Trace];
80
81        for level_filter in levels.iter().rev() {
82            let level = if let Some(level) = level_filter.to_level() {
83                level
84            } else {
85                // off is the last level, just do nothing in that case
86                continue;
87            };
88            let meta = log::Metadata::builder().level(level).build();
89            if self.enabled(&meta) {
90                log::set_max_level(*level_filter);
91                break;
92            }
93        }
94
95        log::set_boxed_logger(Box::new(self))
96    }
97    pub fn multi(&self) -> MultiProgress {
98        self.bar.clone()
99    }
100}
101
102impl<L: Log> Log for LogWrapper<L> {
103    fn enabled(&self, metadata: &log::Metadata) -> bool {
104        self.log.enabled(metadata)
105    }
106
107    fn log(&self, record: &log::Record) {
108        // do an early check for enabled to not cause unnescesary suspends
109        if self.log.enabled(record.metadata()) {
110            self.bar.suspend(|| self.log.log(record))
111        }
112    }
113
114    fn flush(&self) {
115        self.log.flush()
116    }
117}