Skip to main content
Laravel, shipping fast.

Log everything, but log it smartly. Include context such as who made the request, what they were doing, and what went wrong.

// config/logging.php
return [
    'default' => env('LOG_CHANNEL', 'stack'),

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

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

        'slack' => [
            'driver' => 'slack',
            'url' => env('LOG_SLACK_WEBHOOK_URL'),
            'username' => 'Laravel Alert',
            'level' => 'critical', // Only critical errors to Slack
        ],
    ],
];

// Log with rich context
class DomainService
{
    public function licenses(Bearer $bearer): Collection
    {
        Log::info('Fetching domains', [
            'bearer_id' => $bearer->id,
            'bearer_name' => $bearer->name,
            'timestamp' => now()->toIso8601String(),
        ]);

        try {
            return LicenseFacade::licenses();
        } catch (Exception $e) {
            // Log the failure with full context
            Log::error('Failed to fetch domains', [
                'bearer_id' => $bearer->id,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }
}

When something goes wrong, your logs tell the entire story. Not just "error happened" but "which user, when, and why."

Request Logging Middleware

// app/Http/Middleware/LogApiRequests.php
class LogApiRequests
{
    public function handle(Request $request, Closure $next): Response
    {
        $start = microtime(true);

        // Don't log health checks (noise)
        if ($request->is('*/health')) {
            return $next($request);
        }

        $response = $next($request);

        $duration = round((microtime(true) - $start) * 1000); // ms

        Log::channel('api')->info('API Request', [
            'method' => $request->getMethod(),
            'path' => $request->getPathInfo(),
            'status' => $response->getStatusCode(),
            'duration_ms' => $duration,
            'bearer_id' => auth()->id(),
            'remote_ip' => $request->ip(),
        ]);

        return $response;
    }
}

Error Code System

Create an enum of error codes for consistency. When an error happens, you return a machine-readable code, not just a message:

enum ErrorCode: string
{
    // Validation (4xx)
    case INVALID_INPUT = 'INVALID_INPUT';
    case VALIDATION_FAILED = 'VALIDATION_FAILED';
    case MISSING_FIELD = 'MISSING_FIELD';

    // Authentication (401)
    case INVALID_TOKEN = 'INVALID_TOKEN';
    case TOKEN_EXPIRED = 'TOKEN_EXPIRED';

    // Authorization (403)
    case INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS';

    // Not Found (404)
    case RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND';
    case LICENSE_NOT_FOUND = 'LICENSE_NOT_FOUND';

    // Rate Limiting (429)
    case RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED';

    // Server (5xx)
    case INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR';
    case PROVIDER_ERROR = 'PROVIDER_ERROR';
}

// In exception handler
if ($exception instanceof LicenseNotFound) {
    return response()->json([
        'error' => 'License not found',
        'code' => ErrorCode::LICENSE_NOT_FOUND->value,
    ], 404);
}

Error codes let API consumers write smarter client code. Instead of checking error messages (fragile!), they check codes.


Summary

Error handling:

  1. Format all errors consistently
  2. Never expose stack traces to clients
  3. Log everything internally
  4. Use appropriate HTTP status codes

Logging:

  1. Log with context (who, what, when)
  2. Use different log channels for different severity
  3. Log at the boundaries (request/response)
  4. Keep logs for compliance but clean them up

Custom exceptions:

  1. Create domain-specific exception classes
  2. Use enums for error codes
  3. Handle different exceptions differently
  4. Make errors help, not hurt

Good error handling turns confusion into clarity. Your future self, debugging at 2 AM, will thank you.