The visitor needs a status code, an apology, what did not happen and a reference to quote; the developer needs the exception. A correlation ID, one random value per request, joins them in a response header, the page and every log line. Accept a proxy's X-Request-Id only if it looks like an ID, since whatever a client sends ends up in your logs.
<?php
$id = $_SERVER['HTTP_X_REQUEST_ID'] ?? ''; // set by a proxy, or not
if (!preg_match('/^[A-Za-z0-9-]{8,64}$/', $id)) $id = bin2hex(random_bytes(8));
header("X-Request-Id: $id");
set_exception_handler(function (Throwable $e) use ($id): void {
error_log("[$id] " . $e::class . ": {$e->getMessage()} at "
. basename($e->getFile()) . ":{$e->getLine()}");
http_response_code(500);
echo "<h1>Something went wrong</h1>\n<p>You were not charged. ",
"If you contact us, quote reference <b>$id</b>.</p>\n";
});
throw new PDOException('SQLSTATE[HY000] [2002] Connection refused');Served by php -S 127.0.0.1:8213 errorpage.php 2> logs/server.log, fetched with curl 3,008 -si and the header X-Request-Id: 7f3c9a2e-checkout, then searched with grep -F 7f3c9a2e logs/server.log:
HTTP/1.1 500 Internal Server Error ... X-Request-Id: 7f3c9a2e-checkout Content-type: text/html; charset=UTF-8 <h1>Something went wrong</h1> <p>You were not charged. If you contact us, quote reference <b>7f3c9a2e-checkout</b>.</p> [Wed Sep 23 16:31:06 2026] [7f3c9a2e-checkout] PDOException: SQLSTATE[HY000] [2002] Connection refused at errorpage.php:12
The page names no database or path; the log has it all. Add the ID to Monolog 21,402 records with a processor, and send 503 with Retry-After for maintenance and a 4xx only when the visitor can fix the problem.