Add --log/--remote-log diagnostic tracing for stalled transfers

Adds an optional timestamped trace of transfer-relevant events (tail
position vs. file length, rename detection, finalize, protocol
send/recv) to both the client and server sides, so a stalled transfer
can be diagnosed from correlated local/remote logs instead of guessing.

Notably logs when a source's final size ends up smaller than what was
already read (pos > current_len after rename) -- this can never
satisfy the pos == current_len finalize condition, which is a real
deadlock risk for writers that preallocate a fixed-size file and
truncate down to the actual capture length before renaming.

--log <path> on either the client or `--server` process traces that
process's own events. --log on ClientCli is local-only; --remote-log
<path> is forwarded as the spawned --server process's --log so both
sides of a remote transfer leave a trace, tied together by timestamp.
This commit is contained in:
2026-08-27 17:06:00 -04:00
parent 17e9e5e361
commit b3e7248ac5
7 changed files with 189 additions and 14 deletions
+30
View File
@@ -0,0 +1,30 @@
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
/// Optional diagnostic trace, one timestamped line per event, for diagnosing
/// where a transfer stalls (e.g. which side is still waiting, and on what).
/// `Logger::none()` is a no-op so call sites don't need to branch on whether
/// `--log` was given.
pub struct Logger(Option<Mutex<File>>);
impl Logger {
pub fn none() -> Logger {
Logger(None)
}
pub fn open(path: &Path) -> std::io::Result<Logger> {
let f = OpenOptions::new().create(true).append(true).open(path)?;
Ok(Logger(Some(Mutex::new(f))))
}
pub fn log(&self, args: std::fmt::Arguments) {
let Some(m) = &self.0 else { return };
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
if let Ok(mut f) = m.lock() {
let _ = writeln!(f, "[{:.6}] {}", now.as_secs_f64(), args);
}
}
}