All posts

Laravel Logging Best Practices

Set up production-ready logging in Laravel with channels, contextual data, log rotation, and integration with monitoring services.

Laravel Logging Best Practices

Laravel's logging system is built on Monolog and supports sophisticated multi-channel configurations. Here's how to set it up for production.

Configure Log Channels

// config/logging.php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'stderr'],
    ],
    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => 'info',
        'days' => 14,
    ],
    'stderr' => [
        'driver' => 'monolog',
        'handler' => StreamHandler::class,
        'with' => ['stream' => 'php://stderr'],
        'formatter' => JsonFormatter::class,
    ],
    'audit' => [
        'driver' => 'daily',
        'path' => storage_path('logs/audit.log'),
        'level' => 'info',
        'days' => 90,
    ],
],

Add Contextual Information

// Middleware to add request context
class LogContext
{
    public function handle(Request $request, Closure $next)
    {
        Log::shareContext([
            'request_id' => $request->header('X-Request-ID', Str::uuid()),
            'user_id' => $request->user()?->id,
            'ip' => $request->ip(),
        ]);
        return $next($request);
    }
}

Structured Logging

// Good: structured data
Log::info('Order created', [
    'order_id' => $order->id,
    'user_id' => $order->user_id,
    'total' => $order->total,
    'items_count' => $order->items->count(),
]);

// Bad: string interpolation
Log::info("Order {$order->id} created by user {$order->user_id}");

Log Levels Guide

  • emergency/alert/critical — system is unusable, immediate action needed
  • error — runtime errors, exception caught
  • warning — unusual but handled (retry succeeded, deprecation)
  • info — significant events (user login, order placed)
  • debug — disabled in production

Sensitive Data

// config/logging.php — replace sensitive fields
'tap' => [RedactSensitiveData::class],

class RedactSensitiveData
{
    public function __invoke($logger)
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->pushProcessor(function ($record) {
                // Redact sensitive fields from context
                $sensitive = ['password', 'token', 'credit_card'];
                foreach ($sensitive as $key) {
                    if (isset($record['context'][$key])) {
                        $record['context'][$key] = '[REDACTED]';
                    }
                }
                return $record;
            });
        }
    }
}

Pair your logging setup with Bugsly's error tracking. Logs provide the narrative context; Bugsly captures the exact exception with stack traces and request data.

Try Bugsly Free

Track up to 100 issues per month on the free plan, with unlimited events and no credit card required.

Get Started Free