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

AI-powered error tracking that explains your bugs. Set up in 2 minutes, free forever for small projects.

Get Started Free