Skip to content

Undefined array key 1 in LaravelLog when entry is corrupt/null bytes → HTTP 500 #529

Description

@xaviergainon

Summary

Opening a Laravel daily log file in Log Viewer returns HTTP 500 when the indexed entry is corrupt (e.g. a run of null bytes \x00). The UI fails for the whole file, not just that entry.

Environment

  • opcodesio/log-viewer v3.24.2
  • Laravel 13 / PHP 8.5
  • Shared RWX volume with concurrent writers (daily log under storage/logs/...)

Error

ErrorException: Undefined array key 1 at vendor/opcodesio/log-viewer/src/Logs/LaravelLog.php:41

Relevant stack (abridged):
Opcodes\LogViewer\Logs\LaravelLog->parseText() Opcodes\LogViewer\Logs\Log->__construct('\x00\x00\x00...', ..., $filePosition, $index) Opcodes\LogViewer\Readers\IndexedLogReader->next() Opcodes\LogViewer\Http\Controllers\LogsController->index()

In our case the constructed text was ~328 null bytes at file offset ~512608.

Root cause

In LaravelLog::parseText():

preg_match(static::regexPattern(), array_shift($firstLineSplit), $matches);
$this->datetime = Carbon::parse($matches[1])?->setTimezone(LogViewer::timezone());
If preg_match fails (corrupt / binary / non-Laravel chunk), $matches[1] is undefined. Laravel turns that into an ErrorException, which bubbles up as a 500 on GET /log-viewer/api/logs.

Also, IndexedLogReader::next() only skips empty($text). A string of \x00 is not empty in PHP, so the corrupt chunk is still passed to the parser.

Expected behaviour
Corrupt or non-matching chunks should not take down the API. Prefer one of:

Treat as an unparseable entry (level none, message like [unparseable log entry]), or
Skip the entry and continue pagination
Valid Laravel lines should keep working unchanged.

Suggested fix
Guard before using $matches[1], e.g.:

if text is empty / only null bytes & whitespace → mark unparseable and return
if preg_match(...) !== 1 → mark unparseable and return (do not call Carbon::parse($matches[1]))
Happy to open a PR if that approach works for you.

Workaround
We temporarily register a subclass via LogViewer::extend('laravel', ResilientLaravelLog::class) that catches this case locally.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions