From e8f82d1f3ac3a3282ad42b0a35eb52d0465a3076 Mon Sep 17 00:00:00 2001 From: Angel Coronel Date: Mon, 20 Jul 2026 01:53:45 -0400 Subject: [PATCH 1/2] Avoid warning for empty files too small to fingerprint Signed-off-by: Angel Coronel --- ...1065-empty-file-fingerprint-warning.fix.md | 1 + lib/file-source-common/src/fingerprinter.rs | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1065-empty-file-fingerprint-warning.fix.md diff --git a/changelog.d/1065-empty-file-fingerprint-warning.fix.md b/changelog.d/1065-empty-file-fingerprint-warning.fix.md new file mode 100644 index 0000000000000..4f3f7326447a2 --- /dev/null +++ b/changelog.d/1065-empty-file-fingerprint-warning.fix.md @@ -0,0 +1 @@ +Avoid emitting a warning for empty files that are too small to fingerprint. diff --git a/lib/file-source-common/src/fingerprinter.rs b/lib/file-source-common/src/fingerprinter.rs index 3216d8c2178d4..5390422160cdf 100644 --- a/lib/file-source-common/src/fingerprinter.rs +++ b/lib/file-source-common/src/fingerprinter.rs @@ -202,9 +202,12 @@ impl Fingerprinter { known_small_files: &mut HashMap, emitter: &impl FileSourceInternalEvents, ) -> Option { + let mut file_size = None; + let metadata = match fs::metadata(path).await { Ok(metadata) => { if !metadata.is_dir() { + file_size = Some(metadata.len()); self.fingerprint(path).await.map(Some) } else { Ok(None) @@ -221,7 +224,7 @@ impl Fingerprinter { .map_err(|error| { match error.kind() { ErrorKind::UnexpectedEof => { - if !known_small_files.contains_key(path) { + if file_size != Some(0) && !known_small_files.contains_key(path) { emitter.emit_file_checksum_failed(path); known_small_files.insert(path.to_path_buf(), time::Instant::now()); } @@ -638,6 +641,31 @@ mod test { ); } + #[tokio::test] + async fn no_error_on_empty_file() { + let target_dir = tempdir().unwrap(); + let empty_path = target_dir.path().join("empty.log"); + fs::write(&empty_path, []).unwrap(); + + let mut fingerprinter = Fingerprinter::new( + FingerprintStrategy::FirstLinesChecksum { + ignored_header_bytes: 0, + lines: 1, + }, + 1024, + false, + ); + + let mut small_files = HashMap::new(); + assert!( + fingerprinter + .fingerprint_or_emit(&empty_path, &mut small_files, &NoErrors) + .await + .is_none() + ); + assert!(!small_files.contains_key(&empty_path)); + } + #[test] fn test_monotonic_compression_algorithms() { // This test is necessary to handle an edge case where when assessing the magic header From 4a67d8d131e2e99f6569c4eff88bc4a264c3c6d1 Mon Sep 17 00:00:00 2001 From: Angel Coronel Date: Sun, 26 Jul 2026 03:47:11 -0400 Subject: [PATCH 2/2] Keep empty files tracked when suppressing fingerprint warning Signed-off-by: Angel Coronel --- lib/file-source-common/src/fingerprinter.rs | 80 +++++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/lib/file-source-common/src/fingerprinter.rs b/lib/file-source-common/src/fingerprinter.rs index 5390422160cdf..8d41f92072e9b 100644 --- a/lib/file-source-common/src/fingerprinter.rs +++ b/lib/file-source-common/src/fingerprinter.rs @@ -224,8 +224,10 @@ impl Fingerprinter { .map_err(|error| { match error.kind() { ErrorKind::UnexpectedEof => { - if file_size != Some(0) && !known_small_files.contains_key(path) { - emitter.emit_file_checksum_failed(path); + if !known_small_files.contains_key(path) { + if file_size != Some(0) { + emitter.emit_file_checksum_failed(path); + } known_small_files.insert(path.to_path_buf(), time::Instant::now()); } return; @@ -280,7 +282,17 @@ async fn fingerprinter_read_until( #[cfg(test)] mod test { - use std::{collections::HashMap, fs, io::Error, path::Path, time::Duration}; + use std::{ + collections::HashMap, + fs, + io::Error, + path::Path, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, + time::Duration, + }; use async_compression::tokio::bufread::GzipEncoder; use bytes::BytesMut; @@ -642,10 +654,13 @@ mod test { } #[tokio::test] - async fn no_error_on_empty_file() { + async fn fingerprint_or_emit_only_warns_on_non_empty_small_files() { let target_dir = tempdir().unwrap(); let empty_path = target_dir.path().join("empty.log"); + let too_small_path = target_dir.path().join("too_small.log"); + fs::write(&empty_path, []).unwrap(); + fs::write(&too_small_path, b"missing newline").unwrap(); let mut fingerprinter = Fingerprinter::new( FingerprintStrategy::FirstLinesChecksum { @@ -656,14 +671,26 @@ mod test { false, ); + let emitter = RecordingEmitter::default(); let mut small_files = HashMap::new(); + assert!( fingerprinter - .fingerprint_or_emit(&empty_path, &mut small_files, &NoErrors) + .fingerprint_or_emit(&empty_path, &mut small_files, &emitter) .await .is_none() ); - assert!(!small_files.contains_key(&empty_path)); + assert_eq!(emitter.checksum_failed_count(), 0); + assert!(small_files.contains_key(&empty_path)); + + assert!( + fingerprinter + .fingerprint_or_emit(&too_small_path, &mut small_files, &emitter) + .await + .is_none() + ); + assert_eq!(emitter.checksum_failed_count(), 1); + assert!(small_files.contains_key(&too_small_path)); } #[test] @@ -685,6 +712,47 @@ mod test { #[derive(Clone)] struct NoErrors; + #[derive(Clone, Default)] + struct RecordingEmitter { + checksum_failed: Arc, + } + + impl RecordingEmitter { + fn checksum_failed_count(&self) -> usize { + self.checksum_failed.load(Ordering::SeqCst) + } + } + + impl FileSourceInternalEvents for RecordingEmitter { + fn emit_file_added(&self, _: &Path) {} + + fn emit_file_resumed(&self, _: &Path, _: u64) {} + + fn emit_file_watch_error(&self, _: &Path, _: Error) {} + + fn emit_file_unwatched(&self, _: &Path, _: bool) {} + + fn emit_file_deleted(&self, _: &Path) {} + + fn emit_file_delete_error(&self, _: &Path, _: Error) {} + + fn emit_file_fingerprint_read_error(&self, _: &Path, _: Error) {} + + fn emit_file_checkpointed(&self, _: usize, _: Duration) {} + + fn emit_file_checksum_failed(&self, _: &Path) { + self.checksum_failed.fetch_add(1, Ordering::SeqCst); + } + + fn emit_file_checkpoint_write_error(&self, _: Error) {} + + fn emit_files_open(&self, _: usize) {} + + fn emit_path_globbing_failed(&self, _: &Path, _: &Error) {} + + fn emit_file_line_too_long(&self, _: &BytesMut, _: usize, _: usize) {} + } + impl FileSourceInternalEvents for NoErrors { fn emit_file_added(&self, _: &Path) {}