Skip to main content
Errors & Logging

Errors & Logging

Laravel centralizes error handling in an exception handler and sends log messages through configurable channels. Good logging answers what failed, where, and for which request without leaking secrets to visitors.

Debug mode

With APP_DEBUG=true, errors show a detailed stack trace page. In production set it to false so visitors see a generic error page and details go to logs.

APP_ENV=production
APP_DEBUG=false
LOG_CHANNEL=stack
LOG_LEVEL=warning

Logging

use Illuminate\Support\Facades\Log;

Log::debug('Import started', ['file' => $path]);
Log::info('User invited', ['user_id' => $user->id]);
Log::warning('Quota nearly reached', ['team_id' => $team->id]);
Log::error('Payment failed', ['order' => $order->id]);
Log::critical('Database unavailable');

The stack channel can write to several destinations at once, such as a daily file and an external service. Channel definitions live in config/logging.php.

Context

Attach shared context once per request instead of repeating fields in every message.

Log::withContext([
    'request_id' => $request->header('X-Request-Id'),
    'user_id' => $request->user()?->id,
]);

Reporting exceptions

To log an exception but continue, use report(). To log and stop, throw it. Throwing is usually clearer unless the failure is genuinely optional.

try {
    $service->sync();
} catch (SyncException $e) {
    report($e);
}

throw new \RuntimeException('Import failed');

HTTP exceptions

abort(404);
abort(403, 'You cannot edit this post.');
abort_if(! $post->isPublished(), 404);

Custom error pages live in resources/views/errors and are named by status code, for example 404.blade.php or 500.blade.php.

Customizing the handler

Modern Laravel configures exception rendering in bootstrap/app.php. You can render specific exceptions as JSON or redirects without touching the core handler.

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (SyncException $e, Request $request) {
        return $request->expectsJson()
            ? response()->json(['message' => 'Sync failed'], 503)
            : back()->withErrors(['sync' => 'Sync failed']);
    });
})

Operational habits

  • Set a maximum log level in production to avoid gigabytes of debug output.
  • Rotate or ship logs off the server; local files disappear when an instance is replaced.
  • Never log passwords, tokens, full card numbers, or complete request bodies.
  • Use a request identifier so a user report can be matched to a log entry.
  • Alert on error-rate changes rather than reading logs only after a complaint.