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}