生产环境抛 `Undefined index` 时,我是先骂用户还是先查日志?

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

上周凌晨两点被监控叫醒,群里截图一张:某用户提交表单后页面白屏,报错就一行 Undefined index: refer_code。我脑子还没开机,手已经去翻源码了——结果在 Controller 里找了二十分钟,最后发现是前端把字段名从 referCode 改成 refer_code,而服务端接的是驼峰。

这种低级错踩多了,我慢慢养成个习惯:看到 Exception 先别急着定位代码行,先分个类。下面是我攒的"报错脸谱",按症状粗暴分成四堆,对应排查优先级:

一、数组访问类:Notice 变 Fatal 的温水煮青蛙

Undefined indexUndefined offsetTrying to access array offset on value of type null——PHP 8 之前 Notice 不挡道,升级到 8.0 直接给你甩 Fatal。我现在的做法是:开发环境开 error_reporting(E_ALL),生产环境用自定义 handler 把 Notice 记进单独日志,定期扫一遍。

快速定位:先看触发请求的参数快照(我加了中间件自动记),再比对前后端字段命名规范。如果是 API 对接,十有八九是文档没同步。

二、类型撞墙类:从 "传个字符串就行" 到 "必须传对象" 的断崖

Argument 1 passed to xxx() must be of the type string, array given——这种我称为"接口老龄化"。早期图省事用弱类型,后面加了类型声明又没扫全量调用点。最阴间的是框架自动解析的请求参数,你以为拿到的是 string,其实是 Illuminate\Http\UploadedFile

快速定位:看 stack trace 里第一个进入你业务代码的位置,检查该参数的源头。如果是 ThinkPHP,重点盯 input()/param() 的过滤规则;Laravel 的话先看 FormRequest 有没有写错规则导致透传了原始值。

三、数据库翻脸类:SQL 没错但执行就炸

SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceededDeadlock found when trying to get lock 这俩兄弟,日志里经常成对出现。我早期一见就加索引,后来发现有时是事务里嵌了外部 HTTP 请求,锁持有时间被人为拉长。

快速定位:MySQL 开 innodb_print_all_deadlocks = ON,看 LATEST DETECTED DEADLOCK 节里的事务持有和等待顺序。如果是锁等待超时,用 SHOW ENGINE INNODB STATUS 抓现场,重点看 ---TRANSACTION 块里的 MySQL thread id,再关联慢查询日志。

四、依赖失踪类:Class not found 的罗生门

这个版块之前有人写过 Redis 扩展的 case,我补个更隐蔽的:Class 'App\Services\FooService' not found,文件明明在,命名空间也没写错。最后发现是 Linux 文件名大小写和类名不一致——本地 Windows 开发,Git 没开 core.ignorecase=false,提交到 Linux 生产环境就裂了。

快速定位:先 composer dump-autoload 排除 PSR-4 映射缓存,再 ssh 到服务器用 ls -la 核对实际文件名。如果是 Docker,还要检查 volume 挂载有没有把本地大小写不敏感的文件系统带进去。

最后说个邪门的

有回生产环境间歇性抛 Allowed memory size exhausted,不是大数据量查询,也不是递归死循环。查了三天,发现是某个定时任务用了 file_get_contents 拉远程图片做缩略图,对方 CDN 偶发返回 302 到自身形成循环,PHP 默默跟了几十次重定向直到内存炸掉。后来全换成了带 max_redirects 限制的 Guzzle。

现在我的 Exception 处理中间件会额外记三样东西:请求峰值内存、外部 HTTP 调用耗时和次数、当前数据库连接数。很多报错单看堆栈是盲人摸象,配上这三维数据,真凶基本跑不掉。

你们有没有遇到过"报错信息完全误导方向"的 case?我上面那个 302 循环算一个,想听听更离谱的。

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