Fatal error 和 Exception 长得太像?聊聊我花了两年才分清的救命排查逻辑

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

前两天群里有个兄弟截图问:"这报错 Fatal error: Uncaught Exception 到底算 Fatal 还是 Exception?"我一看乐了,两年前我也在这个坑里扑腾过。今天不聊具体代码,就说说我是怎么建立"报错类型→定位方向"的肌肉记忆的,纯属个人经验,不一定对,欢迎拍砖。

一、先断生死:Fatal 是终点,Exception 是中途抛出的信号

刚开始我老觉得带 Exception 字样的就是"异常",能 try-catch 兜住。实际上 Fatal error 里的 Uncaught Exception 意思是:异常抛出来了,但没人接,PHP 只好替你 fatal 掉。这时候看调用栈比看错误类型重要一百倍。

我的土办法:报错第一行如果带 "Fatal error:",直接滑到文件路径和行号,那才是真正的案发现场。中间那些 Class 'xxx' not found 之类的,是死因,不是病因。

二、三类我最常撞墙的 Exception 及其"惯犯现场"

1. PDOException / "SQLSTATE[HY000]" 系列

这类我踩过最阴的坑:本地 MySQL 8.0,线上 5.7,认证插件不兼容。报错是 PDOException,但真正的锅在连接阶段,不在 SQL 语句。我的快速定位:先看 SQL 有没有打印到日志,没打印 = 连接层就炸了;打印了但执行报错 = 看具体 SQL 和参数绑定。

另外,ThinkPHP 里如果开了断线重连,PDOException 可能被吞掉换成更模糊的提示,这时候我习惯临时在 database.php 里把 break_reconnect 关掉,让原样抛出来。

2. RuntimeException / "Unable to create lock file" 这类

宝塔环境特别爱出这个。缓存目录、session 目录、日志目录,权限 755 但用户组不对,或者 SELinux 开着。我的排查顺序:先 ls -Z 看安全上下文,再 getfacl 看 ACL,最后才轮到 chmod 777(虽然生产环境不建议)。

有个邪门经历:OSS 挂载到本地目录后,RuntimeException 报的是本地路径,实际是 OSS 的 endpoint 间歇性抽风。现在我看到带路径的 RuntimeException,会先 df -h 确认是不是挂载点。

3. TypeError / ArgumentCountError

PHP 8 之后这货变多了,严格模式下一言不合就炸。最烦的是框架中间件或者事件回调里参数对不上,报错栈指到框架内部,不是业务代码。我的野路子:从栈底往上数,找到第一个属于自己项目命名空间的文件,那附近就是传参传错的地方。

三、我现在的"三秒定方向"习惯

手机收到报错告警,来不及开电脑的时候,我就看三个东西:异常类名前缀(PDO/Runtime/Type)、文件路径里有没有 vendor(有就是依赖问题)、行号是不是 1 或者特别大的数字(1 通常是语法/加载问题,特别大可能是生成的缓存文件)。

这三点定个大概方向,再决定是回滚、降配置还是热修复。比一上来就翻代码快得多,尤其适合凌晨两点脑子不清醒的时候。

四、一个可能没人提的细节:Exception 的 code 字段

很多框架和扩展抛异常时会带个 code,但 PHP 原生 Exception 的 code 是 int,有些扩展偷懒塞了字符串进去,结果 json_encode 的时候整个报错对象序列化失败,日志里变成空。我现在写监控上报,一律先 (int)$e->getCode(),再截断到 255,省了很多麻烦。

大家有没有那种"报错类型和实际原因八竿子打不着"的奇葩经历?比如我有一次 CDN 回源超时,最后发现是 SSL 证书链不完整,但报错是 522,跟证书半毛钱关系看不出来。这种反直觉的坑,最适合拿出来互相提醒了。

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