Tail Laravel Logs in Real Time with Pail (Filters, Levels, and Users)

Laravel Pail tails your logs in real time from the terminal. Filter by level, message, and user, works with any driver including Sentry, and beats tail -f.

Steven Richardson
Steven Richardson
· 6 min read

For years my debugging loop was tail -f storage/logs/laravel.log in one terminal pane and the app in another. It works until it doesn't: the file fills with noise, you can't filter by level or user, and the moment you switch your logs to Sentry or syslog in staging, tail shows you nothing because there's no file to follow.

Laravel Pail fixes all of that. It's a first-party package that tails your application logs in real time from the command line, works with any log driver, and gives you filters that understand Laravel's log structure. Here's the complete setup, from install to the combined filters I actually use when chasing a production bug.

Install Laravel Pail via Composer#

Pail ships as a dev dependency. Pull it in with Composer — there's nothing to publish and no config file to manage:

composer require --dev laravel/pail

One hard requirement: Pail needs the PCNTL PHP extension, which it uses for the signal handling behind the live stream. PCNTL ships with most Unix PHP builds but isn't available on native Windows. If you develop on Windows, run Pail inside Laravel Sail, Docker, or WSL2 where PCNTL is present. You can confirm the extension is loaded with php -m | grep pcntl.

Start streaming logs with php artisan pail#

With the package installed, start tailing. The php artisan pail command opens a live, color-coded view of every log entry as it's written:

php artisan pail

Output is intelligently truncated by default so a single noisy exception doesn't flood your terminal. When you need the full picture, bump the verbosity: -v shows untruncated messages, and -vv prints complete exception stack traces.

# Untruncated output
php artisan pail -v

# Full stack traces
php artisan pail -vv

Press Ctrl+C to stop the stream at any time. Unlike tail, this isn't reading a file — Pail hooks into Laravel's logging layer, which is why the next sections work regardless of where your logs actually go.

Filter the stream by log level and message#

Watching every entry scroll past is barely better than tail. The point of Pail is the filtering. Start with the --level option to show only entries at a given log level — exactly what you want when you're hunting errors and don't care about info chatter:

php artisan pail --level=error

To match on the log message text itself, use --message. It looks only at the message, ignoring exception types and stack traces, so it's precise:

php artisan pail --message="Payment failed"

When you want a wider net, --filter searches across the type, file, message, and stack trace content. It's the right tool for following a specific exception class wherever it surfaces:

php artisan pail --filter="QueryException"

The distinction matters in practice: reach for --message when you know the exact string you logged, and --filter when you're chasing an exception type or a file path through the noise.

Narrow logs to a single authenticated user#

This is the filter that makes Pail worth installing on its own. When one customer reports a bug you can't reproduce, --user restricts the stream to log entries written while that user was authenticated:

php artisan pail --user=1

Pass the user's ID and every line you see was generated by their requests. No more correlating timestamps by hand or grepping for an email across a 40MB log file. (--user is an alias for --auth, so the two are interchangeable.) This pairs naturally with Laravel's debugging tools like Telescope and Pulse — Pail is the fast terminal-first option when you don't want to leave the CLI.

Combine Laravel Pail filters for production debugging#

The filters compose. Stack them to build a precise live view — say, only error-level entries, for one user, matching a specific exception:

php artisan pail --user=42 --level=error --filter="TokenMismatchException"

Because Pail taps the logging layer rather than a file, this still works when your production logs go to a service instead of disk. If you run a self-hosted error tracker, the same command tails your app even when entries are bound for a self-hosted Sentry instance — something tail -f simply cannot do. It's also a sharp way to watch queue workers misbehave in real time, which complements a proper production queue setup when jobs start failing under load.

Gotchas and Edge Cases#

A few things that have tripped me or my team up:

The PCNTL requirement is the big one. A fresh composer install on a Windows host fails outright because Pail's platform requirement can't be met. Either move development into Sail/WSL2, or — if you only need the rest of your dependencies locally — install with --ignore-platform-req=ext-pcntl, accepting that the pail command won't run there.

Pail tails new entries only. It's a live stream, not a log reader, so it won't show you anything that was written before you started the command. For historical digging you still want your file or your log service's search UI.

Finally, remember it's a dev dependency. Pail is a debugging tool you run interactively, not something to wire into production request handling. Keep it under require-dev so it never ships in your production autoloader.

Wrapping Up#

Pail replaces tail -f with a filtered, driver-agnostic live stream, and the --user filter alone will save you the next time a bug only reproduces for one account. Install it as a dev dependency, learn the three filters — --level, --message, --filter — and combine them when you're narrowing in.

From here, it's worth folding Pail into the rest of your local setup: see how it fits alongside the other tools in my Laravel developer toolchain for 2026, and if you want formatting handled before code ever lands, wire up Pint with a Git pre-commit hook.

FAQ#

What is Laravel Pail used for?

Laravel Pail tails your application's logs in real time directly in the terminal. It's used for debugging and monitoring while you work — watching errors as they happen, following a specific exception, or isolating the activity of a single user — without opening log files or a dashboard. Because it taps Laravel's logging layer rather than a file, it works with any log driver.

How do I filter logs by level in Laravel Pail?

Use the --level option followed by the log level you care about, for example php artisan pail --level=error. Pail recognises the standard RFC 5424 levels Laravel uses — emergency, alert, critical, error, warning, notice, info, and debug — so you can scope the stream to exactly the severity you're chasing and ignore the rest.

Does Laravel Pail work with Sentry or only local logs?

It works with any log driver, including Sentry and Flare, not just the local file. This is the key difference from tail -f: the standard tail command can only follow a physical file on disk, so it goes blank the moment your logs are routed to an external service. Pail hooks into Laravel's logging layer, so it tails your app's log entries wherever they're configured to go.

How do I tail logs for a specific user with Pail?

Pass the authenticated user's ID to the --user option, for example php artisan pail --user=1. Pail then shows only the log entries written while that user was authenticated, which is the fastest way to reproduce a bug that only affects one account. --user is an alias for --auth, and you can combine it with --level or --filter to narrow further.

Why does Laravel Pail require the PCNTL extension?

Pail uses the PCNTL (process control) extension for the Unix-style signal handling that powers its live, interruptible stream — including responding cleanly to Ctrl+C. PCNTL isn't available on native Windows, so on Windows you should run Pail inside Laravel Sail, Docker, or WSL2. You can check whether it's loaded with php -m | grep pcntl.

Steven Richardson
Steven Richardson

CTO at Digitonic. Writing about Laravel, architecture, and the craft of leading software teams from the west coast of Scotland.