Loading...

This is taking longer than expected.

Back to the help centre

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:

  1. Create a Slack app with a bot token that has chat:write, and invite the bot to the channel.
  2. 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's activeComponent 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.