dcat/laravel-log-viewer
A Laravel package that adds a clean web interface for browsing, searching, filtering, and tailing application log files. Inspect errors and stack traces, switch between log files, and download logs without SSH access.
Install the package via Composer:
composer require dcat/laravel-log-viewer
The service provider is auto-discovered. Publish the configuration so you can see the route prefix, middleware, and log-file pattern the viewer uses:
php artisan vendor:publish --provider="Dcat\LogViewer\LogViewerServiceProvider"
That creates config/log-viewer.php (route path, middleware stack, and the glob pattern used to list files in storage/logs). After publishing, confirm the route is registered before you touch anything else:
php artisan route:list | grep -i log
By default the viewer is reachable at a URL like /log-viewer (check the published config for the exact prefix). Open it in a browser while in local and you get a file list on the left and a rendered log tail on the right — good enough to triage a 500 during development without tail -f.
Where to look first:
config/log-viewer.php — route, middleware, pattern.vendor/dcat/laravel-log-viewer/src/LogViewerServiceProvider.php — how routes and views are wired, useful if you need to override.vendor/dcat/laravel-log-viewer/src/Http/Controllers/LogViewerController.php — the actions behind list/view/download/delete.First real use case: drop it into a staging environment behind auth so a support engineer can pull the last exception stack trace and download the log file without SSH access.
Lock it down before anything else. The package ships a web middleware stack and no auth by default. Add your auth guard in the published config:
// config/log-viewer.php
'middleware' => ['web', 'auth', 'can:viewLogs'],
Gate by environment. Either wrap the route registration in your own AppServiceProvider:
if (app()->environment(['local', 'staging'])) {
// rely on package route
}
…or, more robustly, disable the package route in config and register your own inside a guarded group:
Route::middleware(['web', 'auth', 'role:admin'])
->prefix('admin/logs')
->group(function () {
Route::get('/', [\Dcat\LogViewer\Http\Controllers\LogViewerController::class, 'index']);
});
Integrating into a Dcat Admin panel. Because the author maintains Dcat Admin, the viewer is often mounted as an admin extension. Register the package route under the same admin middleware group and prefix as your panel so session/auth state carries over, then link to it from a menu item. Keep the prefix distinct from your panel's own routes to avoid collisions.
Rotated and named logs. The config exposes a filename pattern (typically *.log). If you rely on daily files (laravel-2024-06-01.log) or custom channels writing to storage/logs/*.log, confirm the glob matches. Adjust the pattern to something like *.log and *.log.* if you keep compressed or rotated backups around.
Downloading and clearing from within the app. The controller actions back the "download" and "delete" buttons in the UI, but you can also drive the same behavior from Artisan commands or health checks by reading the file directly:
$path = storage_path('logs/laravel.log');
$tail = collect(file($path))
->slice(-200)
->implode('');
For programmatic log triage, prefer streaming with SplFileObject and seek over file_get_contents, since a busy day's log can easily exceed PHP's memory limit.
Support-ticket workflow. Grant a support role read-only access via middleware; use the download button to attach the log to the ticket; revoke the role when the incident closes. This is the pattern the package is genuinely good at — it saves a round-trip to the server.
Custom branding / layout. Publish the views if you need the viewer to match your admin UI:
php artisan vendor:publish --tag=log-viewer-views
Then override the Blade template in resources/views/vendor/log-viewer/.
No auth by default is the number-one pitfall. If you deploy this to production without a middleware change you've publicly exposed stack traces, DB credentials in exceptions, and internal hostnames. Treat the default route as a local-only tool until you've confirmed route:list -v shows your auth middleware on it.
Route caching eats config edits. After changing the route prefix or middleware, run php artisan route:clear && php artisan config:clear. If you're using php artisan config:cache in deployment, the viewer's config values are frozen at cache time — a "my middleware change didn't apply" symptom is almost always a stale config cache.
Large logs will blow up the request. The viewer loads file content to render it. A 500 MB laravel.log will time out or exhaust memory. Rotate aggressively (set daily in config/logging.php), and if you must inspect a huge file, use the download button plus less on your workstation rather than rendering in the browser.
File permission errors look like empty lists. The web user (www-data, forge, etc.) needs read access to storage/logs and write access only if you enable deletion from the UI. An empty file list with no error usually means the glob returned nothing because of permissions or because files live in a subdirectory the pattern doesn't recurse into.
The package is quiet on updates. Last release was August 2023, stars are low, and dependents are zero — this is effectively a personal utility. Pin the version in composer.json, and if you hit incompatibilities with newer Laravel majors (route resolution changes, middleware signature changes), expect to patch locally rather than waiting for an upstream fix. Test the viewer after every Laravel upgrade.
Autodiscovery can fail silently. If the route 404s immediately after install, check that the provider appears in php artisan about or bootstrap/cache/packages.php. In older Laravel setups with dont-discover configured, you'll need to register Dcat\LogViewer\LogViewerServiceProvider manually in config/app.php.
Deletion is destructive and unconfirmed by default. If you expose the delete/clear buttons, make sure the controller's CSRF protection is intact (do not strip VerifyCsrfToken) and consider disabling those actions entirely by hiding the buttons in a published view. There is no soft-delete or backup — once cleared, the log is gone.
Symlinked storage breaks the glob. If storage/logs is a symlink (common on Forge/Envoyer releases), glob() follows it on most systems but some hardened PHP setups do not. Point the config at the resolved real path if the list comes back empty.
Extension points are limited. The clean overrides are: config (route, middleware, pattern), the view (via vendor:publish), and route registration (by disabling the package route and wiring your own controller action). Subclassing the controller works but is not an officially documented seam — keep custom logic in your own controller or middleware so the vendor package stays upgradeable.
How can I help you explore Laravel packages today?