应通过中间件拦截$response->getcode()统计错误次数,因400/500类错误多由框架静默生成响应而非抛异常;app_exception钩子无法捕获路由失败、权限拒绝、验证失败等场景,故必须在中间件handle()中实时计数并按状态码与接口路径标准化存储。

直接统计接口请求错误异常次数,关键不是“捕获所有 Exception”,而是聚焦在 HttpException 和框架实际返回的 HTTP 状态码上——因为 400/500 类错误多数不抛出 PHP 异常,而是由中间件或路由层静默生成响应。
用中间件拦截响应状态码做实时计数
ThinkPHP 的 response 对象在发送前才确定最终状态码,app_end 钩子已太晚(响应可能已发出),必须用中间件在 handle() 返回前读取。错误次数统计应基于真实发出的响应,而非是否抛异常。
- 创建中间件
App\Middleware\ErrorCounter,在handle()最后一行插入:$response->getCode() >= 400 && Cache::inc("error:count:{$response->getCode()}", 1, 86400) - 若需按接口路径细分,key 改为
"error:count:{$response->getCode()}:{$request->url()}",但注意 URL 含参数时需标准化(如去掉token=xxx) - 不要用
Db::insert()同步写库——高并发下易拖慢响应;缓存计数 + 定时任务落库更稳妥
为什么不能只监听 app_exception 钩子
app_exception 只捕获未被 catch 的 PHP 异常,但大量错误根本不会走到这里:路由匹配失败(404)、权限校验拒绝(401/403)、验证失败(422)、JSON 解析失败(500 响应体但无异常抛出)都会绕过该钩子。
- 404 请求通常由
think\exception\HttpException抛出,但框架默认会捕获并渲染,app_exception拿不到原始上下文 - 第三方 API 返回
{"code":500,"msg":"服务不可用"},Http::get()不抛异常,app_exception完全无感知 - 真正可靠的方式是统一从
$response->getCode()入手,它反映客户端实际收到的状态
Redis key 设计与 TTL 设置要点
错误计数必须支持滚动窗口和维度隔离,否则数据会混杂失效。
- 错误码聚合:用
error:count:4xx、error:count:5xx区分大类,TTL 设为3600(1 小时),便于观察趋势 - 具体错误码:如
error:count:500单独设 TTL86400(24 小时),用于长期归档 - 禁止把时间戳塞进 key,例如
error:20260817:500—— 这会导致无法用INCR滚动累加,且清理困难 - 若需按用户 ID 统计错误频次,key 应为
error:user_{$uid}:500,避免用 session_id(CLI 或跨域不可靠)
最易被忽略的是:错误计数必须和正常调用计数使用同一套 key 命名逻辑,否则对比分析时维度错位。比如正常请求用 api:count:/v1/order,错误就该对应 error:count:500:/v1/order,而不是随意拼接。否则“某接口错误率飙升”这种判断就失去依据。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











