500 是什么
500 Internal Server Error 是服务端的兜底错误:服务器遇到了未预期的情况,无法完成请求。RFC 9110 §15.6.1 把它定义为意外状况的通用响应,因此它本身不携带任何根因信息——真正的原因必须在服务端日志里找。它和 502、504 的分工是:500 表示请求已经到达应用但应用自己崩了,502 表示网关连不上上游或收到无效响应,504 表示网关等待上游超时。
基本信息
| 名称 | 500 Internal Server Error |
|---|---|
| 作用 | 服务端遇到未预期异常时的兜底响应 |
| 规范 | RFC 9110 §15.6.1 |
| 第一现场 | 应用错误日志里的异常堆栈 |
| 与 502、504 区别 | 500 由应用自身产生,502 与 504 由网关产生 |
典型成因
- 代码未捕获异常:空指针、越界、类型转换失败
- 配置错误:数据库连接串、密钥文件路径、依赖注入缺失
- 外部依赖故障:数据库、缓存、第三方接口不可用时没有降级
- 资源耗尽:日志写满磁盘、容器内存超限被系统杀掉
- 依赖升级:库行为变更导致原本可用的调用抛错
- 发布回归:新版本在特定入参下崩溃
排查入口
排查入口
tail -n 200 /var/log/nginx/error.log # 先分清是网关错误还是应用自己返回的 500
tail -n 200 /var/log/php-fpm/error.log # PHP 站点先看进程池是否报错
docker logs --tail 200 <容器名> # 容器化部署直接看标准输出
kubectl logs --tail=200 deploy/<应用名> --previous # 看被重启前那个实例的日志常见成因对照
| 现象 | 可能成因 | 处置方向 |
|---|---|---|
| 同一入参必崩 | 代码缺陷或数据边界未处理 | 按请求编号定位堆栈并修代码 |
| 所有接口同时报错 | 依赖不可用、配置错误、磁盘满 | 查依赖连通性与磁盘使用率 |
| 偶发且时间随机 | 资源耗尽、连接池打满、并发竞态 | 看连接池与内存监控,补充限流 |
| 发布后集中出现 | 版本回归 | 先回滚,保留现场日志再定位 |
生产环境不要把异常堆栈返回给客户端:堆栈包含文件路径、语句与内部结构,属于信息泄漏。但服务端日志必须保留完整堆栈与请求编号,否则排障只能靠猜。
统一错误处理是防 500 的基本功:在应用入口兜住所有异常,记录结构化日志(请求编号、用户标识、入参摘要),对外只返回通用错误体。这样既不泄漏信息,也让排障不必依赖复现。
定位五步
- 先确定错误来源:对比网关访问日志与应用日志,看这次请求到底有没有到达应用
- 再看异常类型:同一种异常反复出现说明是代码路径问题,随机异常先怀疑资源耗尽
- 用请求编号把链路串起来:网关、应用、数据库三处按同一编号交叉比对
- 看时间相关性:与发布、扩容、定时任务重合的报错优先怀疑变更引入
- 复现优先于猜测:用最小请求体复现一次,往往比反复读日志更快定位到具体分支
日志该记什么
| 字段 | 作用 |
|---|---|
| 请求编号 | 把网关、应用与下游日志串成一条链路 |
| 异常类型与堆栈 | 定位到具体代码位置,不要只记错误消息 |
| 入参摘要 | 复现问题的关键,敏感字段必须脱敏 |
| 耗时与下游调用 | 区分是应用自身慢还是依赖拖慢了整体 |
对外错误页保持中性即可:给出请求编号与联系渠道,不要暴露组件名、版本号或数据库信息,也不要让不同错误返回能用肉眼区分的细节。