*/ public function availableFiles(): Collection { return collect(File::glob(storage_path('logs/laravel*.log'))) ->map(fn (string $path) => [ 'name' => basename($path), 'size' => File::size($path), 'modified_at' => date('Y-m-d H:i:s', File::lastModified($path)), 'modified_timestamp' => File::lastModified($path), ]) ->sortByDesc('modified_timestamp') ->values() ->map(fn (array $file) => collect($file)->except('modified_timestamp')->all()); } public function latestFileName(): ?string { return $this->availableFiles()->first()['name'] ?? null; } public function paginated(string $fileName, int $perPage = 25, string $search = '', ?string $level = null): LengthAwarePaginator { $entries = $this->parse($fileName) ->when($level, fn (Collection $q) => $q->where('level', strtoupper($level))) ->when($search, fn (Collection $q) => $q->filter( fn (array $entry) => str_contains(strtolower($entry['message']), strtolower($search)) )) ->values(); $page = request()->integer('page', 1); $items = $entries->forPage($page, $perPage)->values(); return new Paginator( $items, $entries->count(), $perPage, $page, ['path' => request()->url(), 'query' => request()->query()], ); } /** * @return Collection */ private function parse(string $fileName): Collection { $path = $this->resolvePath($fileName); if (! $path) { return collect(); } $lines = preg_split('/\R/', File::get($path)) ?: []; $entries = []; $current = null; foreach ($lines as $line) { if (preg_match(self::ENTRY_PATTERN, $line, $matches)) { if ($current) { $entries[] = $current; } $current = [ 'timestamp' => $matches[1], 'environment' => $matches[2], 'level' => strtoupper($matches[3]), 'message' => $matches[4], 'raw' => $line, ]; } elseif ($current !== null && trim($line) !== '') { $current['message'] .= "\n".$line; $current['raw'] .= "\n".$line; } } if ($current) { $entries[] = $current; } return collect($entries) ->reverse() ->values() ->map(fn (array $entry, int $index) => [...$entry, 'id' => (string) $index]); } private function resolvePath(string $fileName): ?string { $allowed = $this->availableFiles()->pluck('name'); if (! $allowed->contains($fileName)) { return null; } return storage_path('logs/'.$fileName); } }