Logs and debugging
Where errors are written, how often they are reported, how to get them in Slack, and the tools that find slow pages and N+1 queries.
The kit writes a daily log, throttles repeated exceptions so one broken page does not fill it, and can post each new error to Slack. In development it adds the debugbar, an N+1 query detector and a live tail of the log. This guide covers the variables behind each and the two things to do before deploying: protect the log viewer and set the Slack token.
The log viewer at /logs
routes/web.php registers GET /logs with the LogViewerController of the rap2hpoutre/laravel-log-viewer package. It lists the files in storage/logs/, lets you read, download and delete them, and the kit adds no middleware to it: the route is public. Protect it before deploying, either by wrapping it:
Route::get('logs', LogViewerController::class . '@index')->middleware(['auth', 'role:admin']);
or by moving the line to routes/admin.php, where it becomes /admin/logs behind the admin role.
Channels and variables
config/logging.php keeps Laravel's channels plus one of the kit's, slack_api.
| Variable | Default | What it does |
|---|---|---|
LOG_CHANNEL |
daily |
The channel everything is written to. daily rotates storage/logs/laravel-YYYY-MM-DD.log |
LOG_STACK |
daily |
Comma-separated channels used when LOG_CHANNEL=stack |
LOG_LEVEL |
debug |
Minimum level for single, daily, papertrail, stderr, syslog and errorlog; critical for slack |
LOG_DAILY_DAYS |
14 |
Days of daily files kept before rotation deletes them |
LOG_DEPRECATIONS_CHANNEL |
null |
Where PHP and library deprecation notices go; null drops them |
LOG_DEPRECATIONS_TRACE |
false |
Include a stack trace with each deprecation |
LOG_STDERR_FORMATTER |
none | Monolog formatter class for the stderr channel, for containers that collect standard error |
LOG_SYSLOG_FACILITY |
LOG_USER |
Facility for the syslog channel |
LOG_PAPERTRAIL_HANDLER |
SyslogUdpHandler |
Handler class of the papertrail channel |
PAPERTRAIL_URL, PAPERTRAIL_PORT |
none | Host and port of the papertrail channel |
LOG_SLACK_WEBHOOK_URL |
none | Incoming webhook of Laravel's stock slack channel |
LOG_SLACK_USERNAME |
Laravel Log |
Name the stock slack channel posts as |
LOG_SLACK_EMOJI |
:boom: |
Icon of the stock slack channel |
SLACK_BOT_TOKEN |
none | Bot token of the kit's slack_api channel; setting it turns on the error alerts below |
SLACK_LOG_CHANNEL |
#errores |
Slack channel the slack_api channel posts to |
The stock slack channel is a plain Laravel channel: nothing in the kit writes to it unless you add it to LOG_STACK. The kit's error alerts go through slack_api.
Error alerts in Slack
slack_api is a Monolog SlackHandler that talks to the Slack API with a bot token. It posts as Laravel Logs with the :boom: icon, at level error and above, as an attachment that includes the context of the exception except the exception object, the URL and the user id.
To turn it on:
- Create a Slack app with a bot token that has
chat:write, and invite the bot to the channel. - Set the variables:
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_LOG_CHANNEL=#errors
Nothing else changes: the exception handler in bootstrap/app.php checks whether slack_api has a token and, when it does, posts every exception that passes the throttle below. A database connection refused, SQLSTATE[HY000] [2002], never reaches Slack even then, because it is noise the next request fixes. Livewire's Cannot update locked property depends on who sent it: with nobody signed in, it is discarded before it is reported, so it reaches neither the log nor Slack; with a session, it is reported like any other exception, Slack included.
In the local environment none of this runs: the handler writes the exception to the log and stops before the Slack check, so nothing reaches Slack on your machine, token or not. To see an alert arrive, try it in an environment other than local.
How often an exception is reported
bootstrap/app.php decides, in withExceptions, whether an exception is written at all. In the local environment every exception is reported, every time. Elsewhere:
- The message is normalised, replacing numbers and timestamps with placeholders, and hashed with the class name. The count for that signature is kept in the cache for 30 days.
- The exception is reported when the count reaches 1, 10, 25, 50, 100, 300, 500 or 1000, then every 1000 after that.
- Even at those counts, the same signature is reported at most once every 5 minutes.
- Messages listed in
ignoredExceptionMessages(), today only the modal package'sactiveComponent must not be accessed before initialization, are not reported in any environment.
Every report carries extra context: the last non-Livewire URL the person opened, the request path, the user agent, the environment, the Livewire component that was running and how many times this exception has been seen. That is the count the Clear log cache button in The devzone resets, when the cache store is database, so a fixed error is reported again from 1.
The N+1 query detector
beyondcode/laravel-query-detector watches every request and reports a relation loaded in a loop. config/querydetector.php:
| Variable | Default | What it does |
|---|---|---|
QUERY_DETECTOR_ENABLED |
null |
null follows APP_DEBUG; true or false forces it |
QUERY_DETECTOR_THRESHOLD |
1 |
How many times a relation may run before it is reported; 1 reports every repeat |
QUERY_DETECTOR_LOG_CHANNEL |
daily |
Channel used when the Log output is added |
Its output is the debugbar: the findings appear in a Messages tab of the bar. Whitelist a relation that is loaded on purpose in the except array of the config, with both the model class and the relation name:
'except' => [
App\Models\Order::class => [
App\Models\OrderLine::class,
'lines',
],
],
The debugbar
barryvdh/laravel-debugbar is a dev dependency; it shows queries, views, the session and the request timeline at the bottom of every page. config/debugbar.php:
| Variable | Default | What it does |
|---|---|---|
DEBUGBAR_ENABLED |
null |
null follows APP_DEBUG; set false to hide it with debug on |
DEBUGBAR_EDITOR |
phpstorm |
Editor the file links open: phpstorm, vscode, vscode-insiders, vscode-remote, vscode-insiders-remote, vscodium, textmate, emacs, sublime, atom, nova, macvim, idea, netbeans, xdebug or espresso |
DEBUGBAR_THEME |
auto |
auto, light or dark |
DEBUGBAR_OPEN_STORAGE |
false |
Lets anyone open the stored data of previous requests from the bar; never turn it on where the site is public |
DEBUGBAR_LOCAL_SITES_PATH, DEBUGBAR_REMOTE_SITES_PATH |
empty | Map a path inside a container or VM to the path on your machine so editor links resolve |
Never enable it in production: it exposes queries and session data to anyone who loads a page.
Tail the log while developing
composer dev starts the server, the queue worker, Vite and php artisan pail, which prints every log entry in the terminal as it happens. Run php artisan pail on its own to follow the log of a running site, and php artisan pail --filter="QueryException" to see one kind of entry. On a server, follow the daily file instead:
tail -f storage/logs/laravel-$(date +%F).log
The variables to set on a server, these among them, are listed in Deploy your project.