diff --git a/src/config/logging.rs b/src/config/logging.rs index 9224d4986..42d14f027 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -17,6 +17,8 @@ use log4rs::{ use serde::{Deserialize, Serialize}; use serde_with::{DisplayFromStr, serde_as}; +use crate::config::progress_options::multi_progress; + /// Logging Config #[serde_as] #[derive(Default, Debug, Parser, Clone, Deserialize, Serialize, Merge)] @@ -77,6 +79,7 @@ impl LoggingOptions { .target(Target::Stderr) .encoder(Box::new(PatternEncoder::new("{h([{l}])} {m}{n}"))) .build(); + let stdout = PbPauseAppender(stdout); let mut root_builder = Root::builder().appender("stdout"); let mut config_builder = Config::builder().appender( @@ -119,3 +122,22 @@ impl LoggingOptions { Ok(()) } } + +/// A wrapper around [`ConsoleAppender`] that suspends the progress bar when writing logs. +#[derive(Debug)] +struct PbPauseAppender(ConsoleAppender); + +impl log4rs::append::Append for PbPauseAppender { + fn append(&self, record: &log::Record<'_>) -> Result<()> { + multi_progress().suspend(|| self.0.append(record)) + } + + fn flush(&self) { + // as of log4rs 1.4.0, ::flush does nothing, + // so we do not need to pause the progress bar here. In the future, + // if log4rs changes this behavior, we might need to add a suspend here. + // But that's not necessary right now, so we just call flush directly + // to avoid unnecessary suspends. + self.0.flush(); + } +} diff --git a/src/config/progress_options.rs b/src/config/progress_options.rs index 574086309..7c9f1d23f 100644 --- a/src/config/progress_options.rs +++ b/src/config/progress_options.rs @@ -3,11 +3,11 @@ use std::{fmt::Write, time::Duration}; use std::io::IsTerminal; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, OnceLock}; use std::time::Instant; use bytesize::ByteSize; -use indicatif::{HumanDuration, ProgressBar, ProgressState, ProgressStyle}; +use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressState, ProgressStyle}; use clap::Parser; use conflate::Merge; @@ -19,6 +19,19 @@ use serde_with::{DisplayFromStr, serde_as}; use rustic_core::{Progress, ProgressBars, ProgressType, RusticProgress}; +/// Returns the global `MultiProgress` instance used by all interactive progress bars. +/// +/// Must be shared with `indicatif_log_bridge::LogWrapper` so that log output +/// suspends progress bars before printing. +pub fn multi_progress() -> &'static MultiProgress { + static MP: OnceLock = OnceLock::new(); + MP.get_or_init(|| { + let mp = MultiProgress::new(); + mp.set_move_cursor(true); + mp + }) +} + mod constants { use std::time::Duration; @@ -110,7 +123,7 @@ pub struct InteractiveProgress { impl InteractiveProgress { fn new(prefix: &str, kind: ProgressType, tick_interval: Duration) -> Self { let style = Self::initial_style(kind); - let bar = ProgressBar::new(0).with_style(style); + let bar = multi_progress().add(ProgressBar::new(0).with_style(style)); bar.set_prefix(prefix.to_string()); bar.enable_steady_tick(tick_interval); Self { bar, kind }