Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pail Laravel Package

nunomaduro/pail

Pail is an elegant log viewer for Laravel that streams your logs in real time right from the terminal. Watch local or remote logs, filter by level or message, and read color-coded output with ease—no extra setup required.

View on GitHub
Deep Wiki
Context7

Getting Started

Pail is a console log tailer: it watches your Laravel log file and pretty-prints new entries (timestamp, level badge, message, exception traces) as they're written.

Install (Laravel 10.34+/11+ already ships it in require-dev):

composer require nunomaduro/pail --dev
php artisan pail

That's the whole setup. Leave it running in a spare terminal (or tmux pane) while you work. Versions from v1.2.7 onward are the safe baseline — that release hardened two things (malformed JSON lines, auth-user resolution) that used to be able to take the watcher down.

Where to look first:

  1. Run php artisan pail -h — the flag list is small and is the real API surface (--filter, --level, --message, --timeout, -v/-vv, -q).
  2. Look at the dev script in your composer.json. In a fresh Laravel 11 app it already invokes Pail alongside serve, queue:listen and Vite:
"dev": [
    "Composer\\Config::disableProcessTimeout",
    "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \
        \"php artisan serve\" \
        \"php artisan queue:listen --tries=1\" \
        \"php artisan pail --timeout=0\" \
        \"npm run dev\" --names=server,queue,logs,vite"
]

Note --timeout=0 — that's what keeps it alive forever inside composer dev instead of exiting after the default idle window.

Canonical first use case: you're reproducing a bug and want to see your Log::error() lines without tail -f storage/logs/laravel.log | grep and without the multi-line stack-trace noise. Open one terminal with php artisan pail --level=error, hit the endpoint in the browser, watch the error land with a coloured level and collapsed trace.

Implementation Patterns

Watching during active development

The highest-value pattern is the two-pane setup: composer dev in one terminal (server + queue + Vite), and a second terminal scoped to what you care about right now:

# Only errors and above, full exception traces
php artisan pail --level=error -v

# Everything happening in one namespace / feature
php artisan pail --filter="App\\\\Services\\\\Billing"

# A single domain concept across the whole app
php artisan pail --message="invoice"

--level and --filter/--message compose, so you can get very narrow very fast. --filter is a regex matched against the rendered line; --message is a plainer substring match on the message body — use --message when you don't want to think about escaping.

Debugging exceptions

Pail collapses stack traces by default so a single exception doesn't scroll your terminal. Step up verbosity as needed:

  • default — message + level, trace collapsed
  • -v — expands the application frames of the trace
  • -vv — includes vendor frames (useful when the bug is genuinely in a package, e.g. a query builder or a queue driver)

This is the fastest loop for "what exception is this request throwing and where" — you never leave the console.

Queue and scheduled work

Because Pail taps the log file rather than the HTTP layer, it sees queue worker output and scheduler output for free, as long as those processes write to the same log file. Run queue:listen (as composer dev does) rather than queue:work while developing, and Pail becomes your job-level debugger:

php artisan pail --filter="ProcessPodcast" --level=warning

Filtering noise out of stack logs

If you're on LOG_STACK=single,slack or similar, Pail still only shows what lands in the file — good. But if you're drowning in local.DEBUG from a chatty package, a negative-lookahead regex is the escape hatch:

php artisan pail --filter="^(?!.*(HealthCheck|Ping)).*$"

Crude, but it works and beats editing log statements.

JSON-formatted logs

If you write structured logs (a JsonFormatter on the single/daily channel, or a custom tap), Pail renders the raw line and your --filter regex runs against that JSON string — so --message="invoice_id" matches the serialised field name. Since v1.2.7 a malformed line no longer crashes the watcher, which matters here because concurrent workers can interleave a half-written JSON line into the file. Upgrade if you tail JSON output; on older versions a single bad line could end the session.

Configuration and custom watchers

Publish the config when you need to change defaults:

php artisan vendor:publish --tag=pail-config

config/pail.php exposes an enabled flag, a default watcher key, and a watchers map of class names. The shipping watchers are the log-file watcher and a null watcher (a no-op, handy for tests/CI). The extension point is the watchers array: point a key at your own class implementing Pail's watcher contract if you want to tail something other than the default log file — a per-service log, a JSON-lines audit file, a rotated directory.

Staging / shared environments

Pail is a fine read-only tool on a staging box:

ssh staging 'cd /var/www/app && php artisan pail --level=error --timeout=0'

Keep it out of production require and out of anything that would be exposed as a web route — it's an artisan command, not a UI.

Gotchas and Tips

Pail reads the file. If your logs don't go to a file, Pail shows nothing. This is the #1 confusion. With LOG_CHANNEL=stderr (common in Docker images, Sail variants, Octane/FrankenPHP setups) or syslog/errorlog, there is no laravel.log to tail. Fix: switch to single/daily locally, or watch docker compose logs -f / the container's stderr instead.

It starts at the end of the file. Pail seeks to EOF on startup, so pre-existing history isn't replayed. If you want to see what already happened, tail -n 200 storage/logs/laravel.log — or reproduce the request again with Pail already running.

daily driver + rotation. Pail follows today's dated file and handles the file being rotated/recreated, but if your app rolls at midnight while Pail is running, restart it if output stalls. single avoids the question entirely during development.

--timeout default will surprise you. Without --timeout=0, Pail exits after its idle window. If it "randomly quits", that's why — not a crash.

Malformed lines no longer kill the watcher (v1.2.7+). Before v1.2.7 a truncated or corrupt JSON log line could crash Pail outright; that's fixed. If a colleague reports "Pail just dies sometimes", check their pinned version first before hunting for a config problem.

No surprise auth resolution (v1.2.7+). Earlier builds resolved the authenticated user while assembling log context, which could mean an unexpected auth/session/DB path being touched from a console process. That's gone — useful if you tail from contexts with no request lifecycle (queue workers, CI, --timeout=0 long-runners). Don't build anything that depends on Pail doing auth lookup for you; it doesn't.

Shell quoting on regexes. Namespace filters need doubled backslashes: --filter="App\\\\Models" in a double-quoted string. If you're fighting it, use single quotes (--filter='App\\Models') or switch to --message="App\Models"-style substring matching.

Timestamps are the app's timezone, not your shell's. If log lines look "an hour off" from date, check config/app.php → timezone, not Pail.

Permissions. Pail runs as whichever user invoked artisan. If your queue worker or FPM pool writes as www-data and you're tailing as your own user, reads generally work but file rotation/recreation can bite. In Docker, make sure the volume with storage/logs is actually mounted into the container you're running Pail in — otherwise you're tailing an empty in-container path.

Huge log files. Pail is a tailer, not an indexer, so it's cheap — but a single channel file that's grown to hundreds of MB will still slow down rotation/truncation handling. daily with a short retention is the pragmatic dev config.

It's not a replacement for Telescope or Horizon. Pail gives you the log stream, not request payloads, query counts, or job retries. Use Pail for the live feed and Telescope for post-hoc introspection; they're complementary, and running both is normal.

Disable it where it can't work. Set PAIL_ENABLED=false (or flip enabled in config/pail.php) in CI and any environment where composer dev-style scripts might invoke it headlessly — it prevents a hanging or noisy watcher in pipelines.

-q for piping. If you want to feed Pail output somewhere, -q strips decoration. Otherwise the ANSI colouring makes downstream parsing unpleasant.

Keep it a dev dependency. composer require --dev — nothing about it belongs in a production autoload graph, and it adds nothing at runtime since the watcher is only ever started by the artisan command.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core