logFiles = $this->discoverLogFiles(); $this->selectedFile = $this->logFiles[0]['name'] ?? ''; $this->refreshLines(); } public function updatedSelectedFile(): void { $this->refreshLines(); } public function updatedFilter(): void { $this->refreshLines(); } public function updatedLimit(): void { $this->limit = max(50, min(1000, $this->limit)); $this->refreshLines(); } protected function discoverLogFiles(): array { $paths = File::glob(storage_path('logs/*.log')) ?: []; $files = collect($paths) ->map(fn (string $path) => [ 'name' => basename($path), 'path' => $path, 'modified' => File::lastModified($path) ?: 0, 'size' => File::size($path) ?: 0, ]) ->sortByDesc('modified') ->values() ->all(); return $files; } protected function refreshLines(): void { $path = storage_path('logs/'.$this->selectedFile); if (! File::exists($path)) { $this->entries = []; return; } $rawLines = $this->tailFile($path, $this->limit * 2); if ($this->filter !== '') { $needle = mb_strtolower($this->filter); $rawLines = array_values(array_filter( $rawLines, fn (string $line) => str_contains(mb_strtolower($line), $needle) )); } $rawLines = array_slice($rawLines, -$this->limit * 2); $rawLines = $this->mergeStackTraces($rawLines); $rawLines = array_slice(array_reverse($rawLines), 0, $this->limit); $this->entries = array_map([$this, 'formatLine'], $rawLines); } /** * Return last $lines lines of file. Simpler approach is fine for small logs in admin view. */ protected function tailFile(string $path, int $lines = 200): array { try { $contents = File::get($path); } catch (\Throwable) { return []; } $all = preg_split('/\r\n|\r|\n/', (string) $contents) ?: []; return array_slice($all, -$lines); } protected function mergeStackTraces(array $lines): array { $merged = []; foreach ($lines as $line) { $trimmed = ltrim($line); $isStackLine = str_starts_with($trimmed, '#') || str_starts_with($trimmed, 'Stack trace:') || str_starts_with($line, ' '); if ($isStackLine && ! empty($merged)) { $merged[array_key_last($merged)] .= PHP_EOL.$line; continue; } if ($line === '') { continue; } $merged[] = $line; } return $merged; } protected function formatLine(string $line): array { $level = 'info'; $timestamp = null; if (preg_match('/\\[(?[^\\]]+)\\]/', $line, $matches)) { $timestamp = $matches['ts'] ?? null; } if (preg_match('/\\.(EMERGENCY|ALERT|CRITICAL|ERROR|WARNING|NOTICE|INFO|DEBUG)/i', $line, $matches)) { $level = strtolower($matches[1]); } return [ 'text' => $line, 'level' => $level, 'timestamp' => $timestamp, ]; } }