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
| Failure | Example | Response |
|---|---|---|
| Validation | Missing required email | Return field errors; do not log as a system failure. |
| Authorization | User lacks refund permission | Return 403 and record a security event when appropriate. |
| Domain conflict | Attempt to cancel a completed trip | Return a clear conflict or validation response. |
| Infrastructure | Database unavailable | Return a generic 500/503, alert, and preserve detailed logs. |
| Programming defect | Unexpected null or type error | Return 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
- Capture the exact time, user-visible reference, route, and action.
- Find the request in reverse-proxy and application logs.
- Inspect correlated database, queue, and external-service events.
- Reproduce with the same input in a safe environment.
- Fix the root cause and add a regression test.
- Document the failure mode and monitoring improvement.