Paulund
Subscribe

Logging in Laravel

24 October 2026, 4 min read

On this page
  1. What to log
  2. Log levels
  3. Basic usage
  4. Keep messages fixed and put variables in context
  5. Configuring channels
  6. Adding context to every log call
  7. Writing a custom channel
  8. Keeping logs useful and affordable
  9. Storing and querying logs with Grafana Loki

Laravel wraps the Monolog library behind the Log facade. It ships with several log channels, such as single, daily, slack, syslog, errorlog and stderr, and you can send one log call to several channels at once.

What to log

Log the things you will need when something goes wrong in production:

  • Errors and warnings: unexpected failures and the context around them.
  • Events: user logins, background jobs finishing, scheduled tasks running.
  • Requests: the route, how long it took, and the response status.
  • Resource use: slow database queries and cache misses, where they matter.

Log levels

Laravel supports the eight PSR-3 log levels. From least to most severe:

  • debug: detailed diagnostic information.
  • info: normal events, such as a user logging in.
  • notice: normal but significant events.
  • warning: something unexpected happened, but the application can continue.
  • error: a runtime error that stops one operation.
  • critical: a serious error. The application may not be able to continue.
  • alert: someone must act immediately.
  • emergency: the system is unusable.

Use the level to filter. You rarely want debug in production, because it produces a large amount of output.

Basic usage

The Log facade writes to the default channel:

PHP
use Illuminate\Support\Facades\Log;

Log::info('User logged in', ['user_id' => $user->id]);
Log::warning('Disk space running low', ['percent_used' => 87]);
Log::error('Payment failed', ['order_id' => $order->id, 'reason' => $e->getMessage()]);

The second argument is a context array. Laravel stores it alongside the message as structured data.

Keep messages fixed and put variables in context

Each log message should be a fixed string you can search for. Avoid joining variables into the message:

PHP
Log::info('User logged in ' . $user->id);

That message is different for every user, so you cannot search the codebase for it. Pass the variable as context instead:

PHP
Log::info('User logged in', ['user_id' => $user->id]);

Now a search for User logged in finds the exact line of code that wrote the entry, and your log tool can filter on user_id.

Configuring channels

Channels are defined in config/logging.php:

PHP
return [
    'default' => env('LOG_CHANNEL', 'stack'),

    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['daily', 'slack'],
        ],

        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'debug',
        ],

        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => 'info',
            'days' => 14,
        ],

        'slack' => [
            'driver' => 'slack',
            'url' => env('LOG_SLACK_WEBHOOK_URL'),
            'username' => 'Laravel Logs',
            'level' => 'critical',
        ],
    ],
];

The stack channel sends each log call to every channel it lists. Here that means every info entry and above goes to a daily file, and critical errors also post to Slack. The daily driver rotates the file each day and keeps the number of days you set.

To write to one channel without changing the default:

PHP
Log::channel('daily')->info('This goes only to the daily file');
Log::channel('slack')->critical('Alerting the team via Slack');

In Docker or Kubernetes, use the stderr or errorlog channel. The container runtime captures that output and can forward it to your log store.

Adding context to every log call

Log::withContext attaches context to every later log call in the same request:

PHP
Log::withContext([
    'request_id' => $requestId,
    'user_id' => auth()->id(),
]);

Log::info('Processing order');
Log::error('Something went wrong');

Both entries now include request_id and user_id. Call it from a middleware so every request gets an ID you can search for.

Writing a custom channel

If no built-in driver fits, create a custom channel that returns a Monolog logger:

PHP
// config/logging.php
'datadog' => [
    'driver' => 'custom',
    'via' => App\Logging\DatadogLogger::class,
],
PHP
namespace App\Logging;

use Monolog\Logger;

class DatadogLogger
{
    public function __invoke(array $config): Logger
    {
        $logger = new Logger('datadog');
        $logger->pushHandler(new DatadogHandler());

        return $logger;
    }
}

DatadogHandler is your own Monolog handler class that sends records to the service.

Keeping logs useful and affordable

Too much logging slows the app and costs money to store. A few rules keep the volume down:

  • Set a level on each channel, so production only keeps what you need.
  • Never log passwords, card numbers or other personal data.
  • Do not log large objects or whole models. Log the IDs you need to look them up.
  • Only include the context attributes that help you debug. Too many make entries hard to read.

Storing and querying logs with Grafana Loki

A log file on one server stops being enough once you run several instances. Hosted options include the ELK stack, Splunk, Sumo Logic and the logging services from AWS, Azure and Google Cloud.

Grafana Loki is one option. It indexes logs by labels instead of full text, which keeps storage cheaper, and it shows logs next to metrics in Grafana dashboards. You can run it locally with Docker:

Terminal
docker run -d --name=loki -p 3100:3100 grafana/loki:latest

An agent such as Grafana Alloy then ships your log files or container output to it.

You query Loki with LogQL. Fetch every log line for one service:

Text
{job="my-app"}

Find lines containing "error":

Text
{job="my-app"} |= "error"

Parse logfmt lines and filter on a field:

Text
{job="my-app"} | logfmt | duration > 100ms

Logs tell you what happened in one request. To track trends such as response times and error rates, you also need metrics and alerts. Monitoring and alerting in Laravel covers those, and you can turn a log pattern into an alert there too.

Next in Laravel

24 October 2026Monitoring and alerting in Laravel