getgrav/grav · warning · RuntimeException
Cannot read file
Error message
Cannot read file
What it means
LogViewer reads a log file backwards in buffer-sized chunks to extract the last N lines; if fread() ever returns false mid-loop (line 81) it throws 'Cannot read file'. fread only fails like this when the open handle became unreadable or the read was invalid — typically the file was deleted/replaced under the handle, permissions changed, or the handle went stale.
Source
Thrown at system/src/Grav/Common/Helpers/LogViewer.php:81
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
fseek($f, -1, SEEK_END);
if (fread($f, 1) !== "\n") {
--$lines;
}
// Start reading
$output = '';
// While we would like more
while (ftell($f) > 0 && $lines >= 0) {
// Figure out how far back we should jump
$seek = min(ftell($f), $buffer);
// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
$chunk = fread($f, $seek);
if ($chunk === false) {
throw new \RuntimeException('Cannot read file');
}
$output = $chunk . $output;
// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
$lines -= substr_count($chunk, "\n");
}
// While we have too many lines
// (Because of buffer size we might have read too many)
while ($lines++ < 0) {
// Find first newline and remove all text before that
$output = substr($output, strpos($output, "\n") + 1);
}
// Close file and return
fclose($f);
return trim($output);
}View on GitHub (pinned to 6040efed04)
Solutions
- Catch the RuntimeException in your controller and re-open/retry the read once — rotation windows are transient and usually clear immediately.
- Right before reading, verify the file is still readable (is_readable) and its size/inode are sane; skip the read if the file vanished.
- In the UI, degrade gracefully: show 'log temporarily unavailable' rather than a 500 when this fires.
Example fix
// before
$content = LogViewer::extractTail($filepath, $lines);
// after
try {
$content = LogViewer::extractTail($filepath, $lines);
} catch (\RuntimeException $e) {
$content = ''; // file rotated/unreadable mid-read; retry on next request
} Defensive patterns
Strategy: retry
Validate before calling
if (!is_readable($filepath) || filesize($filepath) === 0) {
return ''; // nothing to tail right now
} Try / catch
try {
$content = LogViewer::extractTail($filepath, $lines);
} catch (\RuntimeException $e) {
clearstatcache();
if (is_readable($filepath)) {
$content = LogViewer::extractTail($filepath, $lines); // one retry after rotation
} else {
$content = '';
}
} Prevention
- Treat log reads as best-effort: always wrap viewer calls in try/catch.
- Read logs soon after checking stat; do not hold handles across requests.
- In UI, degrade to 'log temporarily unavailable' instead of erroring out.
When it happens
Trigger: Calling LogViewer's tail extraction while the log file is rotated, truncated, or deleted under the open handle; reading a log whose permissions changed after fopen; handles on flaky network/container storage.
Common situations: Admin log viewer hit at the exact moment logrotate (or Grav's own log rotation) truncates the file; multiple FPM workers where one rotates while another reads; containerized environments with ephemeral log mounts.
Related errors
- Unable to %s: %s
- Unknown error
- Unable to create directory: %s
- ZipArchiver: ZIP failed to extract {archive_file} to {destin
- Failed to commit nonce key file
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/fb9ee2c32e18fce2.
Report an issue: GitHub.