必须弃用 iris-contrib/prometheus 默认中间件,因其直方图分桶失真致P99归零、路径标签未归一化引发内存OOM、缺失service等关键标签导致Grafana下钻失效。

不能直接用 iris-contrib/prometheus 或任何“开箱即用”的中间件上生产——它默认直方图分桶失真、路径标签爆炸、缺关键 service 标签,三者叠加会导致 P99 延迟归零、内存 OOM、Grafana 下钻失效。
为什么 iris-prometheus 默认中间件必须弃用
它暴露的指标看着全,但三个硬伤是线上事故的根源:
-
http_request_duration_seconds直方图用的是prometheus.DefBuckets(最小桶 5ms),而你的 API 多数耗时在 10–200ms,结果 P99 永远卡在0.005,实际可能是 3.2s - 所有原始路径如
/api/v1/users/123直接当path标签值写入,:id动态段不归一化 → 每个用户 ID 生成新指标实例,几分钟吃光内存 - 没预留
service_name、env、version等标签字段,Prometheus 抓到的数据无法按服务或环境切片,Grafana 看板全是平铺乱码
怎么注册指标:必须显式 + 一次 + 启动前
Go 的 Prometheus 客户端对注册极其敏感,错一次就 panic,且不可 recover:
- 所有指标变量(如
httpReqCounter、httpReqDuration)必须定义为包级变量,不能放在 handler 或 init() 里重复 new - 注册只允许在
app.Run()之前做一次:prometheus.MustRegister(httpReqCounter, httpReqDuration) - 别用
promauto.NewCounterVec—— 它会隐式调用MustRegister,多个包 import 就触发重复注册 panic - 如果用了自定义
prometheus.NewRegistry(),后续promhttp.HandlerFor(registry)必须传同一个实例,否则 metrics 返回空
中间件里怎么安全打点:OnResponse + 归一化路径
中间件内不能读 c.StatusCode() 或 c.Params().Get("id") —— 前者返回的是 setter 缓存值,后者在路由匹配完成前不可靠:
立即学习“go语言免费学习笔记(深入)”;
- 耗时统计必须用
c.OnResponse钩子:start := time.Now()放中间件开头,duration := time.Since(start)放OnResponse回调里 - 状态码只能从
c.ResponseWriter().Status()读,不是c.StatusCode() - 路径标签必须归一化:用
c.Route().Path(返回/api/v1/users/:id)替换c.Params()中的占位符,生成/api/v1/users/{id};敏感 query key(如auth_token)要白名单过滤,否则 label 爆炸 - 打点动作只做原子操作:
httpReqCounter.WithLabelValues(method, path, status).Inc()、httpReqDuration.WithLabelValues(method, path).Observe(duration.Seconds())
暴露 /metrics 端点的唯一正确方式
别用 app.Get("/metrics", ...) 或 iris.StaticWeb —— 它们绕过中间件链,且不设 Content-Type: text/plain; version=0.0.4;charset=utf-8,Prometheus 抓取会静默失败:
- 必须用
promhttp.Handler()(或promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))作为标准 HTTP handler - 挂载方式:
http.Handle("/metrics", promhttp.Handler()),或 Iris 下用app.Any("/metrics", iris.FromStd(promhttp.Handler())) - 路径名必须和 Prometheus 配置中
scrape_configs.metrics_path严格一致(默认是/metrics) - 务必加基础鉴权,比如只允许内网 IP 访问,避免监控端点暴露在公网
最易被忽略的点:归一化路径逻辑必须封装成独立函数(如 normalizePath(c *iris.Context)),并在所有中间件统一调用;一旦某个 handler 漏掉,就会混入原始路径,几天后指标 cardinality 暴涨到百万级,Prometheus 自身开始 OOM。



















