laravel/nova-log-viewer
A Laravel Nova tool for viewing your application logs right inside Nova, with polling support. Install via Composer and register LogViewer::make() in your NovaServiceProvider's tools method.
Three commands and one array entry, no publish step, no migrations, no config file:
composer require laravel/nova-log-viewer
// app/Providers/NovaServiceProvider.php
public function tools()
{
return [
// ...
\Laravel\Nova\LogViewer\LogViewer::make(),
];
}
Nothing to compile on your side — the tool ships its own built assets. Restart your queue/OPcache if you're on a config-cached deploy, then refresh Nova and look for the new sidebar entry.
Where to look first. Open the tool once and identify the two controls that will define your day-to-day use: the file selector (v1.0.2 specifically fixed selection when multiple log files exist, so single-plus-daily setups should list laravel.log and each laravel-YYYY-MM-DD.log) and the polling toggle. Everything else is a text view of the file on disk.
First use case. Pick a staging box, throw a deliberate exception at a route (throw new \Exception('log viewer smoke test');), open the tool, and confirm the entry appears — then leave polling on and throw a second one to confirm it arrives without a manual refresh. That ten-minute spike tells you three things at once: the tool has read access to storage/logs, you're looking at the right channel, and polling behaves against your real log volume. Do this on staging before production, because the second part of the check is watching how the UI feels when laravel.log is 40MB rather than 4KB.
Gate it explicitly — there is no RBAC in the package. Out of the box, anyone who can reach Nova can read raw logs. Nova's Tool base class gives you the hooks; use them:
public function tools()
{
return [
\Laravel\Nova\LogViewer\LogViewer::make()
->canSee(fn ($request) => $request->user()->hasRole('admin'))
->canRun(fn ($request) => $request->user()->hasRole('admin')),
];
}
canSee hides the sidebar link; canRun blocks the route. Set both — hiding a menu item is not authorization. If you already have an App\Nova\Tools policy or a viewLogs gate, this is where it goes.
Register per environment. Many teams want this on staging and production but not on local, where tail -f and Telescope are already in muscle memory:
return array_filter([
\Laravel\Nova\LogViewer\LogViewer::make()->canSee($adminOnly),
// ...
]);
Or drop it entirely via $this->app->isLocal(). Keeping local clean avoids a stale second window into the same file you're already watching.
Support triage loop. The workflow that actually pays for the install: a customer reports a 500 at 14:32. Support opens the tool, sets polling on, selects the date-partitioned file for today, and scrolls to the timestamp. They copy the exception class and top stack frame into the ticket before escalating. Engineering receives a triaged ticket instead of "customer says it's broken." The tool has no search across history and no aggregation — so coach support to scan the visible window and paste, not to expect a query interface.
Deploy and job verification. Add a temporary Log::info('deploy marker', ['sha' => $sha]); to your deploy script (or listen to Illuminate\Foundation\Events\Terminating), then watch the tool during a deploy to confirm the new code is live and booting cleanly. The same pattern covers queue workers: a failed job dumps its full stack trace plus serialized payload into laravel.log, and this is often the fastest way to read it without shell access to the box.
Make the logs worth reading. The viewer is only as good as what you write. Structured context renders far better than string concatenation:
Log::error('Payment capture failed', [
'order_id' => $order->id,
'gateway_status' => $response->status(),
]);
If you have a noisy stack, route the signal to a dedicated channel so it lands in its own file and its own tab in the file selector rather than buried in a 60MB default file:
// config/logging.php
'payments' => ['driver' => 'daily', 'path' => storage_path('logs/payments.log'), 'level' => 'debug'],
Log::channel('payments')->error(...) then shows up as a selectable file.
Pair with LOG_LEVEL discipline. In production, LOG_LEVEL=error means the viewer will show you almost nothing. The tool is a window onto whatever your logger writes — if the file is empty, check config/logging.php and .env before assuming the package is broken.
single and daily on local disk work. stderr, syslog, and any shipping driver (CloudWatch, Papertrail, Datadog) leave the app with no file to open, and the viewer shows nothing or a stale remnant. Confirm your default channel before installing.storage/logs is often www-data-owned and laravel.log may be created lazily. If the web user can't read the directory, the file selector comes up empty with no error worth reading. sudo -u www-data ls -la storage/logs is the first debugging step.laravel.log left open in a tab in three people's browsers is a genuine load source during an incident. Mitigate operationally: use daily so files are bounded, rotate/trim aggressively, and close the tab when you're done. The package has no config to tune here — that's the limit, not a setting you've missed.composer show laravel/nova-log-viewer first.> storage/logs/laravel.log (shell redirect) rather than a true rotation can leave the viewer's read offset past the end of the file, showing a blank pane until refresh. Prefer daily channels or logrotate with copytruncate.Tool class, not a config file. There's no publishable config and no per-user log scoping. If you need "each tenant sees only their own log file," that's a fork or a custom tool — don't expect to configure it in.How can I help you explore Laravel packages today?