`Class 'think\exception\HttpException'` 不报错,但页面就是白屏——我被"静默异常"偷走的四小时

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 99 浏览 0 回复

昨晚十一点,用户群里突然炸锅,说移动端文章页打不开。我切到浏览器,F12 一看,HTTP 200,Response 空的,纯白。没有 ThinkPHP 的 trace 页面,没有 Nginx 502,PHP-FPM 也没挂。就像有人把输出流直接掐断了。

我先怀疑是 CDN 回源有问题,清缓存、切直连,一样。又以为是 UniApp 那边接口格式变了,但 PC 端 H5 也是白的。最后只好上服务器翻 `runtime/log`,才发现一条被埋在海量日志里的 `Class 'think\exception\HttpException' not found`,时间戳完全对得上。

真凶是前两天手贱升级了某个 Composer 包,框架核心文件被覆盖了一小块,`\think\exception` 命名空间下的 `HttpException` 类文件权限变成了 000。更坑的是,这个异常是在全局异常捕获器里抛的——异常处理机制自己先崩了,TP 的 `Handle::render` 走不到输出错误页那一步,直接静默退出。

这事儿给我敲了个警钟:不是所有异常都会叫唤。我后来整理了个"哑巴异常"排查清单,分享出来,各位站长可以备着:

一、先确认是不是"真·200"

curl 带上 `-I` 和 `-v`,看 Content-Length 是不是 0,或者有没有被中途截断的 `Transfer-Encoding`。Nginx 的 `error_log` 和 PHP-FPM 的 `slow.log` 要同时开着对比,很多时候问题在网关层就断了,应用层根本不知情。

二、框架级静默,优先查 autoload

Composer 的 `autoload_static.php` 如果和实际文件对不上,或者 `vendor/composer/` 下面有文件被篡改/权限异常,类找不到不会走正常报错,可能直接触发框架底层的 `exit`。我现在的习惯是每次 `composer update` 后跑一遍 `composer dump-autoload --optimize`,再加个 `find vendor -type f ! -perm 644` 扫权限。

三、异常处理器自己抛异常,死循环了

ThinkPHP 的 `app_exception` 如果绑定的类里又写了可能出错的逻辑,比如数据库查询、缓存读取,一旦这些依赖也挂了,就会套娃崩溃。建议异常处理类里只干三件事:记日志、发告警(用文件锁防重)、返回固定错误格式。别在里面做业务。

四、PHP 致命错误被 `register_shutdown_function` 吞了

有些站长喜欢自己注册 shutdown 回调做兜底,但如果回调里再出致命错误,PHP 直接终止,标准输出可能为空。可以在 `shutdown` 函数开头加 `error_get_last()` 判断,并且确保回调里的操作绝对轻量。

最后说个快速定位的野路子:在 `public/index.php` 最开头加 `ob_start()` 和 `register_shutdown_function(function(){ ob_end_flush(); })`,强制刷出缓冲区。如果页面从白屏变成乱码或者半截报错,说明输出是在某个中间节点被截断的,范围一下就缩到 `ob_clean` 或 `exit` 的调用链上了。

那次我恢复文件权限只用了两分钟,但找到问题花了四小时。现在我的监控里除了常规 5xx,还加了一条"200 但响应体小于 100 字节"的告警。白屏比报错更可怕,报错至少知道去哪骂,白屏你只能对着空气挥拳。

评论0
回复 · 0
还没有回复
微信客服 微信客服