laravel/pail
Laravel Pail is a command-line log viewer that lets you dive into your Laravel application's log files right from the terminal. It works with any log driver, including Sentry and Flare, and offers a sleek CLI with useful filters to help you quickly find what you're looking for.
If you are on Laravel 11+, laravel/pail is already in the skeleton's require-dev — composer install is the whole setup. On older apps:
composer require laravel/pail --dev
Then, from the project root:
php artisan pail
That is the entire first use case: reproduce a 500 in the browser, watch the exception and stack trace land in the terminal in real time, with colour-coded levels and no tail -f noise. No config publish, no migration, no service provider registration.
Where to look first
php artisan pail --help — confirm the flags available on your installed version (--filter, --level, --message, --timeout).config/logging.php + your .env — Pail renders whatever your log stack writes, so the channel config (LOG_CHANNEL, LOG_STACK) is the real control surface.storage/logs/laravel.log — the file Pail is almost certainly reading. If you can't see it there, Pail can't see it either.A bounded first run (useful once you don't want a process hanging around):
php artisan pail --timeout=15
Quit an interactive session with Ctrl+C.
The two-pane dev loop. Terminal one runs php artisan serve (or Sail/Valet), terminal two runs php artisan pail. Every request that logs anything — validation failures, slow queries, handled exceptions — scrolls past as you click through the app. This is the highest-value pattern and replaces most ad-hoc dd() calls.
composer dev is the sanctioned version of that. The Laravel 11+ skeleton ships a dev script that runs the server, queue listener, Vite, and Pail concurrently via concurrently. If your team uses it, Pail is already in the loop; if you don't, adding it means nobody has to remember to start the tail.
Filter aggressively — the unfiltered stream is useless within a minute.
# Only lines whose content matches (namespace, ID, exception class...)
php artisan pail --filter="QueryException"
# Only a level and above
php artisan pail --filter="Checkout" --level=error
# Reduce noise: message only, no context/stack
php artisan pail --message
# Scripted / bounded
php artisan pail --timeout=10
--filter matches against the log line content and is regex-flavoured — escape namespace separators (--filter="App\\\\Services\\\\Billing") and always quote the argument so your shell doesn't eat it.
Debugging background work. Because queue workers and the scheduler write to the same log destination as the web process, Pail is an excellent window into them:
php artisan queue:work # pane 1
php artisan pail --filter="ProcessPodcast" # pane 2
php artisan schedule:work # pane 1
php artisan pail --filter="App\\\\Console" # pane 2
Make logs filterable by design. The single best investment alongside Pail is structured context, so you can grep by a business identifier instead of by message prose:
use Illuminate\Support\Facades\Log;
Log::withContext(['order_id' => $order->id, 'user_id' => $user->id]);
Log::info('Payment captured', ['gateway' => 'stripe', 'amount' => $order->total]);
Pail renders the context array under the message (context support landed in v1.1.2), and multi-line entries such as stack traces are grouped rather than split per line (v1.2.3). Then:
php artisan pail --filter="order_id.*1234"
Keep a local sink when you use Sentry/Flare. Pail advertises "any log driver", but the practical mechanism is that a file-backed channel is present in the stack. In Laravel 11+ terms:
LOG_CHANNEL=stack
LOG_STACK=single,sentry
LOG_LEVEL=debug
Drop the file channel and you've left Pail with nothing on disk to render. This is the one config change that makes remote-driver setups work reliably, and it also gives you something to read when the SaaS is down.
JSON logging still works. If you route a channel through Monolog's JsonFormatter, Pail will parse and display it — v1.2.7 specifically hardened this against malformed lines. Same for daily rotation and custom channels: Pail resolves paths from your channel config, so any stream-based handler is fair game.
require-dev. Production images built with composer install --no-dev then simply don't ship it — that is the correct posture. Don't let it drift into require../vendor/bin/sail artisan pail works because it runs inside the container; running php artisan pail on the host while the app logs inside Docker shows you nothing. Same for remote servers and containerised deploys — you must run it where the app runs.laravel.log as www-data and your shell user is you, the file may be unreadable (or, in the reverse case, unwritable by PHP so nothing ever arrives). Check ls -l storage/logs before debugging Pail itself.touch storage/logs/laravel.log (or just trigger a 404) and Pail has something to watch.stderr, syslog, errorlog, and pure-SaaS channels produce no local artefact. If Pail is silent, confirm LOG_CHANNEL/LOG_STACK actually includes a single or daily channel.daily rotation across midnight. The path embeds the date, so a session running past midnight can end up watching yesterday's file. Restart Pail if output goes quiet around 00:00.> storage/logs/laravel.log or a deploy that deletes log files mid-tail can leave the watcher pointing at a stale handle (v1.1.1 fixed a related filemtime error). Restart Pail after any log-file surgery.laravel.log directly or use the --level filter against a raw file — Pail will quietly omit them.>= 1.2.7. Two fixes in that release matter day-to-day: malformed JSON lines no longer crash the tail (one bad line used to take the whole session down), and auth-user resolution is skipped when building context — which is both a correctness and a privacy fix.Log::withContext and request-scoped context will surface user identifiers on screen. Treat "who may run Pail in staging/production, and with what logging level" as a written policy rather than developer discretion; a screen-shared session is a data-exposure path.php artisan test — the failures it logs are already in your test output, and the stream becomes unreadable. Pause it.config/logging.php, Monolog formatters/processors, and disciplined use of Log::withContext. Get those right and Pail becomes the viewer you already wanted.How can I help you explore Laravel packages today?