set_exception_handler仅捕获未被try...catch处理的Exception及其子类异常,不捕获Error、TypeError等;白屏或500常见因注册过晚、回调自身出错或SAPI差异;需配合register_shutdown_function捕获致命错误。

set_exception_handler 能捕获哪些异常
set_exception_handler 只捕获未被 try...catch 捕获的、**未终止脚本执行前抛出的未处理异常**,比如顶层作用域中直接 throw 的 Exception 或其子类。它不捕获 Error(如 FatalError、ParseError)、PHP 7+ 的 TypeError、ArgumentCountError 等错误类型——这些需要 set_error_handler 配合 error_reporting 或单独用 set_exception_handler + set_error_handler 组合兜底。
为什么写了 set_exception_handler 还是看到白屏或500
常见原因有三个:
- 函数注册太晚:必须在异常抛出前调用,例如放在入口文件最顶部,而不是路由分发后才注册
- 回调函数自身出错:如果
set_exception_handler设置的回调里又抛出异常或触发Notice,PHP 会直接 fatal,不再执行任何兜底逻辑 - CLI 和 Web SAPI 行为差异:CLI 下未捕获异常默认打印堆栈;Apache/FPM 下可能被 web server 截断,需确认
display_errors = Off时是否写入了 error log
一个安全可用的全局异常处理器长什么样
核心原则是:不依赖外部服务、不输出 HTML、不抛新异常、记录足够上下文。示例:
function handleUncaughtException($exception) {
// 记录日志(避免 file_put_contents 失败导致二次崩溃)
$log = sprintf(
"[%s] %s: %s in %s:%d\nStack trace:\n%s\n",
date('c'),
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
);
error_log($log, 3, '/var/log/php-uncaught.log');
// CLI 输出简单提示,Web 返回 500 响应头 + 极简内容
if (php_sapi_name() === 'cli') {
fwrite(STDERR, "Uncaught exception: " . $exception->getMessage() . "\n");
} else {
http_response_code(500);
echo 'Internal Server Error';
}
}
set_exception_handler('handleUncaughtException');
注意:error_log(..., 3, ...) 是最稳妥的日志方式;避免在 handler 中调用 echo、var_dump、数据库操作或发送 HTTP 请求。
立即学习“PHP免费学习笔记(深入)”;
和 register_shutdown_function 的关系要不要一起用
要。因为 set_exception_handler 对「致命错误」(Fatal error)完全无效,而 register_shutdown_function 能捕获脚本终止前的状态。典型组合写法:
register_shutdown_function(function () {
if ($error = error_get_last()) {
if (in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("Fatal error: {$error['message']} in {$error['file']}:{$error['line']}", 3, '/var/log/php-fatal.log');
http_response_code(500);
echo 'Internal Server Error';
}
}
});
关键点:必须检查 error_get_last() 是否存在且属于致命类型;不要在 shutdown 函数里再抛异常或触发新错误;两个 handler 都不能依赖 session 或输出缓冲状态。
真正难处理的是「部分资源已释放但脚本尚未退出」的边缘场景,比如在析构函数中抛异常、或扩展层崩溃,这时候连 register_shutdown_function 都可能失效——这种得靠监控和日志聚合提前发现模式,不是单个 handler 能兜住的。



















