From fadad97c67e1ea76b6f984c35746a947e2f8b94d Mon Sep 17 00:00:00 2001 From: Luca Ciucci Date: Wed, 29 Apr 2026 08:25:53 +0200 Subject: [PATCH 1/4] fix: use indicatif-log-bridge to avoid breaking progress bar on log When showing a progress bar, a log message previously caused the bar to be printed again without clearing. `indicatif-log-bridge` solves that by just pausing the progress bar before printing. Unfortunately this intriduces some flickering to the progress bar for unknown reasons. --- Cargo.lock | 11 +++++++++++ Cargo.toml | 1 + src/config/logging.rs | 18 +++++++++++++++++- src/config/progress_options.rs | 19 ++++++++++++++++--- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b742d124..ee9e0c790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,6 +2292,16 @@ dependencies = [ "web-time", ] +[[package]] +name = "indicatif-log-bridge" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63703cf9069b85dbe6fe26e1c5230d013dee99d3559cd3d02ba39e099ef7ab02" +dependencies = [ + "indicatif", + "log", +] + [[package]] name = "indoc" version = "2.0.7" @@ -4290,6 +4300,7 @@ dependencies = [ "globset", "human-panic", "indicatif", + "indicatif-log-bridge", "insta", "itertools 0.14.0", "jaq-core", diff --git a/Cargo.toml b/Cargo.toml index e0d4d8483..4fa31f83e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ futures = { version = "0.3.31", optional = true } globset = "0.4.18" human-panic = "2" indicatif = "0.18" +indicatif-log-bridge = "0.2.3" itertools = "0.14" jiff = "0.2.19" open = "5.3.3" diff --git a/src/config/logging.rs b/src/config/logging.rs index 9224d4986..d51950386 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -3,6 +3,7 @@ use std::{path::PathBuf, sync::OnceLock}; use anyhow::Result; use clap::{Parser, ValueHint}; use conflate::Merge; +use indicatif_log_bridge::LogWrapper; use log::LevelFilter; use log4rs::{ Handle, @@ -17,6 +18,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)] @@ -113,9 +116,22 @@ impl LoggingOptions { if let Some(handle) = HANDLE.get() { handle.set_config(config); } else { - let handle = log4rs::init_config(config)?; + let handle = init_log4rs_config(config)?; _ = HANDLE.set(handle); } Ok(()) } } + +fn init_log4rs_config(config: Config) -> Result { + let logger = log4rs::Logger::new(config); + let handle = logger.handle(); + + // LogWrapper will set the max log level so we need to save it before + // initializing LogWrapper and restore it afterwards. + let max_level = logger.max_log_level(); + LogWrapper::new(multi_progress().clone(), logger).try_init()?; + log::set_max_level(max_level); + + Ok(handle) +} 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 } From d97f4f2f5bad564cbc41b4ac5ba7ad053f8f86de Mon Sep 17 00:00:00 2001 From: Luca Ciucci Date: Thu, 30 Apr 2026 20:58:14 +0200 Subject: [PATCH 2/4] fix: use a custom appender to avoid flickering `indicatif-log-bridge` combined with log4rs appenders cause some flickering of the progress bars. The reason is that `::enabled` does a coarse filtering and single appenders may decide not to print any message. This is the case with our `FileAppender`, which forces many trace and debug messages to be hidden but still flagged as enabled. This causes many unnecessary pauses inside `indicatif-log-bridge`, which uses `log::Log::enabled` as a discriminant. This new approach avoids the issue by wrapping the specific `FileLogger` appender, so that we suspend the progress bar only when the real print happens. --- Cargo.lock | 11 ----------- Cargo.toml | 1 - src/config/logging.rs | 32 ++++++++++++++++++++------------ 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee9e0c790..8b742d124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,16 +2292,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "indicatif-log-bridge" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63703cf9069b85dbe6fe26e1c5230d013dee99d3559cd3d02ba39e099ef7ab02" -dependencies = [ - "indicatif", - "log", -] - [[package]] name = "indoc" version = "2.0.7" @@ -4300,7 +4290,6 @@ dependencies = [ "globset", "human-panic", "indicatif", - "indicatif-log-bridge", "insta", "itertools 0.14.0", "jaq-core", diff --git a/Cargo.toml b/Cargo.toml index 4fa31f83e..e0d4d8483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,6 @@ futures = { version = "0.3.31", optional = true } globset = "0.4.18" human-panic = "2" indicatif = "0.18" -indicatif-log-bridge = "0.2.3" itertools = "0.14" jiff = "0.2.19" open = "5.3.3" diff --git a/src/config/logging.rs b/src/config/logging.rs index d51950386..b2b2744e1 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -3,7 +3,6 @@ use std::{path::PathBuf, sync::OnceLock}; use anyhow::Result; use clap::{Parser, ValueHint}; use conflate::Merge; -use indicatif_log_bridge::LogWrapper; use log::LevelFilter; use log4rs::{ Handle, @@ -92,6 +91,7 @@ impl LoggingOptions { let file_appender = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{d} [{l}] - {m}{n}"))) .build(file)?; + let file_appender = PbPauseAppender(file_appender); root_builder = root_builder.appender("logfile"); config_builder = config_builder.appender( Appender::builder() @@ -116,22 +116,30 @@ impl LoggingOptions { if let Some(handle) = HANDLE.get() { handle.set_config(config); } else { - let handle = init_log4rs_config(config)?; + let handle = log4rs::init_config(config)?; _ = HANDLE.set(handle); } Ok(()) } } -fn init_log4rs_config(config: Config) -> Result { - let logger = log4rs::Logger::new(config); - let handle = logger.handle(); +/// A wrapper around [`FileAppender`] that suspends the progress bar when writing logs. +#[derive(Debug)] +struct PbPauseAppender(FileAppender); - // LogWrapper will set the max log level so we need to save it before - // initializing LogWrapper and restore it afterwards. - let max_level = logger.max_log_level(); - LogWrapper::new(multi_progress().clone(), logger).try_init()?; - log::set_max_level(max_level); +impl log4rs::append::Append for PbPauseAppender { + fn append(&self, record: &log::Record<'_>) -> Result<()> { + multi_progress().suspend(|| { + self.0.append(record) + }) + } - Ok(handle) -} + 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() + } +} \ No newline at end of file From 9af9ff0e72537eec881c6f8b62ae4d8086e3ac7d Mon Sep 17 00:00:00 2001 From: Luca Ciucci Date: Fri, 1 May 2026 16:57:08 +0200 Subject: [PATCH 3/4] fix: wrapped wrong appender --- src/config/logging.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/logging.rs b/src/config/logging.rs index b2b2744e1..ba8e13b3e 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -79,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( @@ -91,7 +92,6 @@ impl LoggingOptions { let file_appender = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{d} [{l}] - {m}{n}"))) .build(file)?; - let file_appender = PbPauseAppender(file_appender); root_builder = root_builder.appender("logfile"); config_builder = config_builder.appender( Appender::builder() @@ -123,9 +123,9 @@ impl LoggingOptions { } } -/// A wrapper around [`FileAppender`] that suspends the progress bar when writing logs. +/// A wrapper around [`ConsoleAppender`] that suspends the progress bar when writing logs. #[derive(Debug)] -struct PbPauseAppender(FileAppender); +struct PbPauseAppender(ConsoleAppender); impl log4rs::append::Append for PbPauseAppender { fn append(&self, record: &log::Record<'_>) -> Result<()> { @@ -135,7 +135,7 @@ impl log4rs::append::Append for PbPauseAppender { } fn flush(&self) { - // as of log4rs 1.4.0, ::flush does nothing, + // 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 From 87438099367b97cbbacdb540102e61faa1158795 Mon Sep 17 00:00:00 2001 From: Luca Ciucci Date: Fri, 1 May 2026 22:13:41 +0200 Subject: [PATCH 4/4] chore: format and fix clippy messages --- src/config/logging.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/config/logging.rs b/src/config/logging.rs index ba8e13b3e..42d14f027 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -129,9 +129,7 @@ struct PbPauseAppender(ConsoleAppender); impl log4rs::append::Append for PbPauseAppender { fn append(&self, record: &log::Record<'_>) -> Result<()> { - multi_progress().suspend(|| { - self.0.append(record) - }) + multi_progress().suspend(|| self.0.append(record)) } fn flush(&self) { @@ -140,6 +138,6 @@ impl log4rs::append::Append for PbPauseAppender { // 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() + self.0.flush(); } -} \ No newline at end of file +}