diff --git a/src/Readers/IndexedLogReader.php b/src/Readers/IndexedLogReader.php index 43f1008f..3d93eda0 100644 --- a/src/Readers/IndexedLogReader.php +++ b/src/Readers/IndexedLogReader.php @@ -146,8 +146,14 @@ public function scan(?int $maxBytesToScan = null, bool $force = false): static $this->file->setMetadata('name', $this->file->name); $this->file->setMetadata('path', $this->file->path); $this->file->setMetadata('size', $this->file->size()); - $this->file->setMetadata('earliest_timestamp', $this->index()->getEarliestTimestamp()); - $this->file->setMetadata('latest_timestamp', $this->index()->getLatestTimestamp()); + // Use the local variables that track ALL logs scanned, not just those matching the query + // This ensures file-level metadata represents the entire file, not filtered results + if (isset($earliest_timestamp)) { + $this->file->setMetadata('earliest_timestamp', $earliest_timestamp); + } + if (isset($latest_timestamp)) { + $this->file->setMetadata('latest_timestamp', $latest_timestamp); + } $this->file->setMetadata('last_scanned_file_position', ftell($this->fileHandle)); $this->file->addRelatedIndex($logIndex); diff --git a/tests/Unit/LogReader/FileMetadataTimestampTest.php b/tests/Unit/LogReader/FileMetadataTimestampTest.php new file mode 100644 index 00000000..d95a8bec --- /dev/null +++ b/tests/Unit/LogReader/FileMetadataTimestampTest.php @@ -0,0 +1,88 @@ +logs(); + $logReader->scan(); + + $initialEarliest = $logFile->earliestTimestamp(); + $initialLatest = $logFile->latestTimestamp(); + + expect($initialEarliest)->toBe($earliestDate->timestamp); + expect($initialLatest)->toBe($latestDate->timestamp); + + // Now scan with a query that only matches the middle log + $logReaderWithQuery = $logFile->logs(); + $logReaderWithQuery->search('/Error occurred/'); + $logReaderWithQuery->scan(); + + // File metadata should still reflect ALL logs, not just matching ones + $finalEarliest = $logFile->earliestTimestamp(); + $finalLatest = $logFile->latestTimestamp(); + + expect($finalEarliest)->toBe($earliestDate->timestamp) + ->and($finalLatest)->toBe($latestDate->timestamp) + ->and($finalEarliest)->toBe($initialEarliest) + ->and($finalLatest)->toBe($initialLatest); +}); + +it('updates file metadata timestamps correctly when scanning without query', function () { + $earliestDate = Carbon::parse('2024-01-01 10:00:00'); + $latestDate = Carbon::parse('2024-01-31 14:00:00'); + + $logContent = implode(PHP_EOL, [ + makeLaravelLogEntry($earliestDate, 'info', 'First log entry'), + makeLaravelLogEntry($latestDate, 'info', 'Last log entry'), + ]); + + $logFile = generateLogFile('test-metadata-update.log', $logContent); + + // Scan without query + $logReader = $logFile->logs(); + $logReader->scan(); + + expect($logFile->earliestTimestamp())->toBe($earliestDate->timestamp) + ->and($logFile->latestTimestamp())->toBe($latestDate->timestamp); +}); + +it('does not overwrite file metadata with filtered timestamps when query matches subset', function () { + // Create logs with dates spanning a month, but query only matches middle logs + $date1 = Carbon::parse('2024-01-01 10:00:00'); // Won't match query + $date2 = Carbon::parse('2024-01-15 12:00:00'); // Will match query + $date3 = Carbon::parse('2024-01-20 13:00:00'); // Will match query + $date4 = Carbon::parse('2024-01-31 14:00:00'); // Won't match query + + $logContent = implode(PHP_EOL, [ + makeLaravelLogEntry($date1, 'info', 'First log entry'), + makeLaravelLogEntry($date2, 'error', 'Error: something went wrong'), + makeLaravelLogEntry($date3, 'error', 'Error: another issue'), + makeLaravelLogEntry($date4, 'info', 'Last log entry'), + ]); + + $logFile = generateLogFile('test-filtered-metadata.log', $logContent); + + // Scan with query that only matches logs with "Error:" in them + $logReader = $logFile->logs(); + $logReader->search('/Error:/'); + $logReader->scan(); + + // File metadata should show full date range (Jan 1 - Jan 31), not filtered range (Jan 15 - Jan 20) + // This is the core bug fix: file metadata should reflect ALL logs, not just filtered ones + expect($logFile->earliestTimestamp())->toBe($date1->timestamp) + ->and($logFile->latestTimestamp())->toBe($date4->timestamp); +});