Error Handling, Logging, and Observability

Errors should fail safely for the user while leaving enough structured evidence for an operator to understand what happened.

Separate Expected and Unexpected Failures

FailureExampleResponse
ValidationMissing required emailReturn field errors; do not log as a system failure.
AuthorizationUser lacks refund permissionReturn 403 and record a security event when appropriate.
Domain conflictAttempt to cancel a completed tripReturn a clear conflict or validation response.
InfrastructureDatabase unavailableReturn a generic 500/503, alert, and preserve detailed logs.
Programming defectUnexpected null or type errorReturn 500, capture stack trace, and fix the defect.

Global Exception Handler

<?php
declare(strict_types=1);

set_exception_handler(static function (Throwable $exception): void {
    $requestId = $_SERVER['HTTP_X_REQUEST_ID'] ?? bin2hex(random_bytes(8));

    error_log(json_encode([
        'level' => 'error',
        'event' => 'unhandled_exception',
        'request_id' => $requestId,
        'type' => $exception::class,
        'message' => $exception->getMessage(),
        'file' => $exception->getFile(),
        'line' => $exception->getLine(),
        'trace' => $exception->getTraceAsString(),
    ], JSON_UNESCAPED_SLASHES));

    http_response_code(500);
    header('Content-Type: text/plain; charset=utf-8');
    echo "The application encountered an error. Reference: {$requestId}";
});

Structured Logging

{
  "timestamp": "2026-07-16T13:41:22Z",
  "level": "warning",
  "event": "payment_gateway_timeout",
  "request_id": "req_7c761f23",
  "invoice_id": 214,
  "duration_ms": 5002,
  "attempt": 2
}

Structured fields make logs searchable. Keep messages concise and put changing values in dedicated fields.

Do Not Log Secrets

  • Passwords or password reset tokens
  • Session identifiers
  • API keys and authorization headers
  • Full payment-card information
  • Raw database connection strings
  • Entire request bodies by default

Useful Measurements

Record request duration, database duration, outbound API duration, queue delay, error count, retry count, and important business outcomes. A page can return HTTP 200 while still failing its purpose; business metrics reveal that difference.

Production Debugging Workflow

  1. Capture the exact time, user-visible reference, route, and action.
  2. Find the request in reverse-proxy and application logs.
  3. Inspect correlated database, queue, and external-service events.
  4. Reproduce with the same input in a safe environment.
  5. Fix the root cause and add a regression test.
  6. Document the failure mode and monitoring improvement.