应使用ctx.onresponse钩子在响应写出后捕获状态码和耗时,开头记录start := time.now(),onresponse中调time.since(start)和ctx.responsewriter().status();避免在中间件内直接读ctx.statuscode()或依赖ctx.params()。

中间件里怎么拿到请求耗时和状态码
直接读 ctx.Values().Get("status") 或 ctx.ResponseWriter().Status() 是错的——Iris 的响应状态码在写入前不可见,中间件执行时响应还没发出去。正确做法是用 ctx.OnResponse 钩子,在响应真正写出后捕获状态码和耗时。
常见错误是把耗时计算放在中间件开头和结尾之间,但这样漏掉了后续 handler(比如控制器)的执行时间,测出来比实际小得多。
- 在中间件开头调用
start := time.Now() - 立即注册
ctx.OnResponse(func() { duration := time.Since(start) }) - 状态码必须从
ctx.ResponseWriter().Status()读,不是ctx.StatusCode()(后者只是 setter) - 别在
OnResponse里调用ctx.StatusCode()或写响应,会 panic
如何避免指标采集拖慢正常请求
高频打点(比如每请求都写 Prometheus Counter)容易成为性能瓶颈,尤其在高并发下。Iris 默认的 iris.Logger 就曾因同步写日志导致 QPS 下降 15%+。
关键不是“要不要采”,而是“怎么轻量地采”。生产环境不建议在中间件里做格式化、序列化或网络上报。
- 只存原始数值:int64 耗时、int 状态码、string 路径,别拼 JSON
- 用原子操作更新计数器,如
metrics.TotalRequests.Add(1),避免锁 - 聚合类指标(如 P95 延迟)交给外部服务(Prometheus + Histogram)算,中间件只打点
- 若需记录慢请求详情,加条件:仅当
duration > 500 * time.Millisecond才记录 traceID 和路径
为什么 ctx.Params() 和路由匹配结果在中间件里不可靠
如果你在中间件里调用 ctx.Params().Get("id") 想统计某类资源接口的调用量,大概率拿不到值——因为 Iris 的参数解析发生在路由匹配之后、中间件链执行期间,但顺序不保证。有些中间件(比如 JWT 验证)可能在参数解析前就执行并提前返回,此时 ctx.Params() 还是空的。
更稳的方式是用 ctx.Path() 做粗粒度分类,或者在 app.UseRouter(路由级中间件)中处理,它确保路由已匹配、参数已解析。
- 优先用
ctx.Path()匹配前缀,例如strings.HasPrefix(ctx.Path(), "/api/v1/users/") - 需要精确参数值时,改用
app.UseRouter注册中间件,而非app.Use - 避免在认证类中间件里依赖
ctx.Params(),它可能为空且不会报错,静默失效
指标字段命名和标签怎么设计才不踩坑
随意用 ctx.GetHeader("User-Agent") 当标签会导致 cardinality 爆炸(每个浏览器版本都成新标签),Prometheus 内存暴涨甚至 OOM。Iris 中间件本身不约束指标结构,但设计不当会让监控系统崩溃。
真实线上出过问题:一个用完整 URL 当 label 的 counter,一天生成 200 万个唯一 series,直接卡死 Prometheus。
- 路径统一用路由模板,不是原始 URL:
/api/v1/users/{id:long},而不是/api/v1/users/123 - HTTP 方法转大写字符串,别用
ctx.Method()原始值(可能带空格或大小写混用) - 用户标识类字段(如
X-User-ID)要白名单校验长度和字符集,防注入式标签污染 - 不要把 error message 当标签值,用固定 code(如
"auth_failed")代替动态文本
最易被忽略的是 OnResponse 的执行时机:它在 WriteHeader 之后、WriteBody 之前触发,但如果你的 handler 用了流式响应(比如 ctx.Stream),这个钩子可能只触发一次,漏掉分块耗时。真要测流式接口,得在 handler 内部手动埋点。











