From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 875A11FF136 for ; Mon, 09 Mar 2026 17:21:48 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id EEE5A3B57; Mon, 9 Mar 2026 17:21:36 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v5 07/11] server: pull: prefix log messages and add error context Date: Mon, 9 Mar 2026 17:20:46 +0100 Message-ID: <20260309162050.1047341-9-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260309162050.1047341-1-c.ebner@proxmox.com> References: <20260309162050.1047341-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1773073229557 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.005 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: L5ETPZVPJ7U6KVNRHGAQWPDOHVLSHFRG X-Message-ID-Hash: L5ETPZVPJ7U6KVNRHGAQWPDOHVLSHFRG X-MailFrom: c.ebner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Pulling groups and therefore also snapshots in parallel leads to unordered log outputs, making it mostly impossible to relate a log message to a backup snapshot/group. Therefore, prefix pull job log messages by the corresponding group or snapshot and set the error context accordingly. Also, reword some messages, inline variables in format strings and start log lines with capital letters to get consistent output. Example output for a sequential pull job: ``` ... Snapshot ct/100/2025-01-15T12:29:44Z: start sync Snapshot ct/100/2025-01-15T12:29:44Z: sync archive pct.conf.blob Snapshot ct/100/2025-01-15T12:29:44Z: sync archive root.pxar.didx Snapshot ct/100/2025-01-15T12:29:44Z: archive root.pxar.didx: downloaded 171.851 MiB (111.223 MiB/s) Snapshot ct/100/2025-01-15T12:29:44Z: sync archive catalog.pcat1.didx Snapshot ct/100/2025-01-15T12:29:44Z: archive catalog.pcat1.didx: downloaded 180.195 KiB (19.884 MiB/s) Snapshot ct/100/2025-01-15T12:29:44Z: got backup log file client.log.blob Snapshot ct/100/2025-01-15T12:29:44Z: sync done ... ``` Example output for a parallel pull job: ``` ... Snapshot ct/100/2025-01-15T12:29:44Z: start sync Snapshot ct/100/2025-01-15T12:29:44Z: sync archive pct.conf.blob Snapshot ct/100/2025-01-15T12:29:44Z: sync archive root.pxar.didx Snapshot vm/200/2025-01-15T12:30:06Z: start sync Snapshot vm/200/2025-01-15T12:30:06Z: sync archive qemu-server.conf.blob Snapshot vm/200/2025-01-15T12:30:06Z: sync archive drive-scsi0.img.fidx Snapshot ct/100/2025-01-15T12:29:44Z: archive root.pxar.didx: downloaded 171.851 MiB (206.124 MiB/s) Snapshot ct/100/2025-01-15T12:29:44Z: sync archive catalog.pcat1.didx Snapshot ct/100/2025-01-15T12:29:44Z: archive catalog.pcat1.didx: downloaded 180.195 KiB (1.972 MiB/s) Snapshot ct/100/2025-01-15T12:29:44Z: got backup log file client.log.blob Snapshot ct/100/2025-01-15T12:29:44Z: sync done ... Signed-off-by: Christian Ebner --- src/server/pull.rs | 93 +++++++++++++++++++++++++++------------------- src/server/sync.rs | 7 ++-- 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/server/pull.rs b/src/server/pull.rs index b11e93e6c..2ed78b840 100644 --- a/src/server/pull.rs +++ b/src/server/pull.rs @@ -149,6 +149,7 @@ async fn pull_index_chunks( index: I, encountered_chunks: Arc>, backend: &DatastoreBackend, + prefix: &str, ) -> Result { use futures::stream::{self, StreamExt, TryStreamExt}; @@ -244,7 +245,7 @@ async fn pull_index_chunks( let chunk_count = chunk_count.load(Ordering::SeqCst); info!( - "downloaded {} ({}/s)", + "{prefix}: downloaded {} ({}/s)", HumanByte::from(bytes), HumanByte::new_binary(bytes as f64 / elapsed.as_secs_f64()), ); @@ -289,6 +290,8 @@ async fn pull_single_archive<'a>( encountered_chunks: Arc>, backend: &DatastoreBackend, ) -> Result { + let prefix = format!("Snapshot {}", snapshot.dir()); + let archive_name = &archive_info.filename; let mut path = snapshot.full_path(); path.push(archive_name); @@ -298,22 +301,29 @@ async fn pull_single_archive<'a>( let mut sync_stats = SyncStats::default(); - info!("sync archive {archive_name}"); + info!("{prefix}: sync archive {archive_name}"); + + let prefix = format!("Snapshot {}: archive {archive_name}", snapshot.dir()); reader.load_file_into(archive_name, &tmp_path).await?; - let mut tmpfile = std::fs::OpenOptions::new().read(true).open(&tmp_path)?; + let mut tmpfile = std::fs::OpenOptions::new() + .read(true) + .open(&tmp_path) + .context(format!("archive {archive_name}"))?; match ArchiveType::from_path(archive_name)? { ArchiveType::DynamicIndex => { let index = DynamicIndexReader::new(tmpfile).map_err(|err| { - format_err!("unable to read dynamic index {:?} - {}", tmp_path, err) + format_err!( + "archive {archive_name}: unable to read dynamic index {tmp_path:?} - {err}" + ) })?; let (csum, size) = index.compute_csum(); - verify_archive(archive_info, &csum, size)?; + verify_archive(archive_info, &csum, size).context(format!("archive {archive_name}"))?; if reader.skip_chunk_sync(snapshot.datastore().name()) { - info!("skipping chunk sync for same datastore"); + info!("{prefix}: skipping chunk sync for same datastore"); } else { let stats = pull_index_chunks( reader @@ -323,6 +333,7 @@ async fn pull_single_archive<'a>( index, encountered_chunks, backend, + &prefix, ) .await?; sync_stats.add(stats); @@ -330,13 +341,15 @@ async fn pull_single_archive<'a>( } ArchiveType::FixedIndex => { let index = FixedIndexReader::new(tmpfile).map_err(|err| { - format_err!("unable to read fixed index '{:?}' - {}", tmp_path, err) + format_err!( + "archive {archive_name}: unable to read fixed index '{tmp_path:?}' - {err}" + ) })?; let (csum, size) = index.compute_csum(); - verify_archive(archive_info, &csum, size)?; + verify_archive(archive_info, &csum, size).context(format!("archive {archive_name}"))?; if reader.skip_chunk_sync(snapshot.datastore().name()) { - info!("skipping chunk sync for same datastore"); + info!("{prefix}: skipping chunk sync for same datastore"); } else { let stats = pull_index_chunks( reader @@ -346,6 +359,7 @@ async fn pull_single_archive<'a>( index, encountered_chunks, backend, + &prefix, ) .await?; sync_stats.add(stats); @@ -354,11 +368,11 @@ async fn pull_single_archive<'a>( ArchiveType::Blob => { tmpfile.rewind()?; let (csum, size) = sha256(&mut tmpfile)?; - verify_archive(archive_info, &csum, size)?; + verify_archive(archive_info, &csum, size).context(prefix.clone())?; } } if let Err(err) = std::fs::rename(&tmp_path, &path) { - bail!("Atomic rename file {:?} failed - {}", path, err); + bail!("archive {archive_name}: Atomic rename file {path:?} failed - {err}"); } backend @@ -386,13 +400,14 @@ async fn pull_snapshot<'a>( is_new: bool, ) -> Result { if is_new { - info!("sync snapshot {}", snapshot.dir()); + info!("{}: start sync", snapshot.dir()); } else if corrupt { info!("re-sync snapshot {} due to corruption", snapshot.dir()); } else { info!("re-sync snapshot {}", snapshot.dir()); } + let prefix = format!("Snapshot {}", snapshot.dir()); let mut sync_stats = SyncStats::default(); let mut manifest_name = snapshot.full_path(); manifest_name.push(MANIFEST_BLOB_NAME.as_ref()); @@ -405,7 +420,8 @@ async fn pull_snapshot<'a>( let tmp_manifest_blob; if let Some(data) = reader .load_file_into(MANIFEST_BLOB_NAME.as_ref(), &tmp_manifest_name) - .await? + .await + .context(prefix.clone())? { tmp_manifest_blob = data; } else { @@ -415,21 +431,21 @@ async fn pull_snapshot<'a>( if manifest_name.exists() && !corrupt { let manifest_blob = proxmox_lang::try_block!({ let mut manifest_file = std::fs::File::open(&manifest_name).map_err(|err| { - format_err!("unable to open local manifest {manifest_name:?} - {err}") + format_err!("{prefix}: unable to open local manifest {manifest_name:?} - {err}") })?; let manifest_blob = DataBlob::load_from_reader(&mut manifest_file)?; Ok(manifest_blob) }) .map_err(|err: Error| { - format_err!("unable to read local manifest {manifest_name:?} - {err}") + format_err!("{prefix}: unable to read local manifest {manifest_name:?} - {err}") })?; if manifest_blob.raw_data() == tmp_manifest_blob.raw_data() { if !client_log_name.exists() { reader.try_download_client_log(&client_log_name).await?; }; - info!("no data changes"); + info!("{prefix}: no data changes"); let _ = std::fs::remove_file(&tmp_manifest_name); return Ok(sync_stats); // nothing changed } @@ -468,7 +484,7 @@ async fn pull_snapshot<'a>( match manifest.verify_file(&filename, &csum, size) { Ok(_) => continue, Err(err) => { - info!("detected changed file {path:?} - {err}"); + info!("{prefix}: detected changed file {path:?} - {err}"); } } } @@ -478,7 +494,7 @@ async fn pull_snapshot<'a>( match manifest.verify_file(&filename, &csum, size) { Ok(_) => continue, Err(err) => { - info!("detected changed file {path:?} - {err}"); + info!("{prefix}: detected changed file {path:?} - {err}"); } } } @@ -488,7 +504,7 @@ async fn pull_snapshot<'a>( match manifest.verify_file(&filename, &csum, size) { Ok(_) => continue, Err(err) => { - info!("detected changed file {path:?} - {err}"); + info!("{prefix}: detected changed file {path:?} - {err}"); } } } @@ -507,7 +523,7 @@ async fn pull_snapshot<'a>( } if let Err(err) = std::fs::rename(&tmp_manifest_name, &manifest_name) { - bail!("Atomic rename file {:?} failed - {}", manifest_name, err); + bail!("{prefix}: Atomic rename file {manifest_name:?} failed - {err}"); } if let DatastoreBackend::S3(s3_client) = backend { let object_key = pbs_datastore::s3::object_key_from_path( @@ -546,7 +562,7 @@ async fn pull_snapshot<'a>( }; snapshot .cleanup_unreferenced_files(&manifest) - .map_err(|err| format_err!("failed to cleanup unreferenced files - {err}"))?; + .map_err(|err| format_err!("{prefix}: failed to cleanup unreferenced files - {err}"))?; Ok(sync_stats) } @@ -562,9 +578,12 @@ async fn pull_snapshot_from<'a>( encountered_chunks: Arc>, corrupt: bool, ) -> Result { + let prefix = format!("Snapshot {}", snapshot.dir()); + let (_path, is_new, _snap_lock) = snapshot .datastore() - .create_locked_backup_dir(snapshot.backup_ns(), snapshot.as_ref())?; + .create_locked_backup_dir(snapshot.backup_ns(), snapshot.as_ref()) + .context(prefix.clone())?; let result = pull_snapshot( params, @@ -585,11 +604,11 @@ async fn pull_snapshot_from<'a>( snapshot.as_ref(), true, ) { - info!("cleanup error - {cleanup_err}"); + info!("{prefix}: cleanup error - {cleanup_err}"); } return Err(err); } - Ok(_) => info!("sync snapshot {} done", snapshot.dir()), + Ok(_) => info!("{prefix}: sync done"), } } @@ -619,6 +638,7 @@ async fn pull_group( group: &BackupGroup, shared_group_progress: Arc, ) -> Result { + let prefix = format!("Group {group}"); let mut already_synced_skip_info = SkipInfo::new(SkipReason::AlreadySynced); let mut transfer_last_skip_info = SkipInfo::new(SkipReason::TransferLast); @@ -710,11 +730,11 @@ async fn pull_group( .collect(); if already_synced_skip_info.count > 0 { - info!("{already_synced_skip_info}"); + info!("{prefix}: {already_synced_skip_info}"); already_synced_skip_info.reset(); } if transfer_last_skip_info.count > 0 { - info!("{transfer_last_skip_info}"); + info!("{prefix}: {transfer_last_skip_info}"); transfer_last_skip_info.reset(); } @@ -807,7 +827,7 @@ async fn pull_group( // Update done groups progress by other parallel running pulls local_progress.done_groups = shared_group_progress.load_done(); local_progress.done_snapshots = pos as u64 + 1; - info!("percentage done: group {group}: {local_progress}"); + info!("{prefix}: percentage done: {local_progress}"); let stats = result?; // stop on error sync_stats.add(stats); @@ -826,12 +846,12 @@ async fn pull_group( } if snapshot.is_protected() { info!( - "don't delete vanished snapshot {} (protected)", - snapshot.dir() + "{prefix}: don't delete vanished snapshot {} (protected)", + snapshot.dir(), ); continue; } - info!("delete vanished snapshot {}", snapshot.dir()); + info!("{prefix}: delete vanished snapshot {}", snapshot.dir()); params .target .store @@ -1031,10 +1051,7 @@ pub(crate) async fn pull_store(mut params: PullParameters) -> Result { errors = true; - info!( - "Encountered errors while syncing namespace {} - {err}", - &namespace, - ); + info!("Encountered errors while syncing namespace {namespace} - {err}"); } }; } @@ -1113,7 +1130,7 @@ async fn pull_ns( list.sort_unstable(); info!( - "found {} groups to sync (out of {unfiltered_count} total)", + "Found {} groups to sync (out of {unfiltered_count} total)", list.len() ); @@ -1182,7 +1199,7 @@ async fn pull_ns( if !local_group.apply_filters(¶ms.group_filter) { continue; } - info!("delete vanished group '{local_group}'"); + info!("Delete vanished group '{local_group}'"); let delete_stats_result = params .target .store @@ -1191,7 +1208,7 @@ async fn pull_ns( match delete_stats_result { Ok(stats) => { if !stats.all_removed() { - info!("kept some protected snapshots of group '{local_group}'"); + info!("Kept some protected snapshots of group '{local_group}'"); sync_stats.add(SyncStats::from(RemovedVanishedStats { snapshots: stats.removed_snapshots(), groups: 0, @@ -1214,7 +1231,7 @@ async fn pull_ns( Ok(()) }); if let Err(err) = result { - info!("error during cleanup: {err}"); + info!("Error during cleanup: {err}"); errors = true; }; } diff --git a/src/server/sync.rs b/src/server/sync.rs index 17d736c41..416bc943d 100644 --- a/src/server/sync.rs +++ b/src/server/sync.rs @@ -138,13 +138,13 @@ impl SyncSourceReader for RemoteSourceReader { Some(HttpError { code, message }) => match *code { StatusCode::NOT_FOUND => { info!( - "skipping snapshot {} - vanished since start of sync", + "Snapshot {}: skipped because vanished since start of sync", &self.dir ); return Ok(None); } _ => { - bail!("HTTP error {code} - {message}"); + bail!("Snapshot {}: HTTP error {code} - {message}", &self.dir); } }, None => { @@ -178,7 +178,8 @@ impl SyncSourceReader for RemoteSourceReader { bail!("Atomic rename file {to_path:?} failed - {err}"); } info!( - "got backup log file {client_log_name}", + "Snapshot {snapshot}: got backup log file {client_log_name}", + snapshot = &self.dir, client_log_name = client_log_name.deref() ); } -- 2.47.3