gin中间件中不能new+register指标,因mustregister()是全局强约束操作,重复注册同名指标会触发无法recover的panic;指标须声明为包级变量,注册仅在init()或main()开头执行一次。

为什么 Gin 中间件里不能 New + Register 指标
因为 prometheus.MustRegister() 是全局强约束操作,重复注册同名指标会直接 panic: duplicate metrics collector registration,且无法 recover。中间件函数每次被调用都会执行一遍逻辑,如果把 prometheus.NewCounterVec() 和 prometheus.MustRegister() 放在里面,每来一个请求就新建+注册一次,进程秒挂。
真正安全的做法是:
- 所有指标变量(如
httpReqCounter、httpReqDurationHist)必须声明为包级变量 - 注册动作只在
init()或main()开头执行一次 - 若使用
promauto包(如promauto.NewCounterVec),它已内置自动注册,再调MustRegister()就是双注册,必 panic - 标签名数组(如
[]string{"method", "status", "path"})必须与后续WithLabelValues("GET", "200", "/api/user")的参数顺序严格一致,错一位就 panic
Gin 中间件中打点的正确姿势
中间件负责在请求进入和响应返回时采集数据,但打点本身不涉及注册,只调用已注册指标的 Inc()、Observe() 等方法。关键在于:打点时机、标签取值、错误容忍。
常见错误包括:
- 从
c.Request.URL.Path直接取原始路径(含 ID),导致标签爆炸(cardinality 爆炸),例如/user/123、/user/456→ 应统一为/user/:id - 未捕获 panic 导致耗时统计中断,
hist.Observe()没执行,直方图缺失数据 - 对
CounterVec调用WithLabelValues()时传了空字符串或非法字符(如空格、斜杠),触发 Prometheus 内部校验失败 - 在 defer 中调用
Observe()但没做时间差计算,或用了time.Since(start)却在 handler 外层定义 start,导致时间不准
推荐写法:在中间件开头记录 start := time.Now(),用 defer 包裹打点逻辑,并用 c.Errors.Last() 或 c.Writer.Status() 获取状态码。
如何让 /metrics 路由在 Gin 里真正生效
promhttp.Handler() 返回的是 http.Handler 接口,Gin 的路由系统不认这个类型,直接 router.GET("/metrics", promhttp.Handler()) 会编译报错或运行时 panic。必须用 gin.WrapH() 做适配。
正确绑定方式:
-
router.GET("/metrics", gin.WrapH(promhttp.Handler()))—— 使用默认注册器 - 若用了自定义注册器(如
reg := prometheus.NewRegistry()),必须写成gin.WrapH(promhttp.HandlerFor(reg, promhttp.HandlerOpts{})),否则指标不会出现在响应里 - 别在
gin.WrapH()外再套一层自定义func(c *gin.Context),会导致 Accept 头协商失败,Prometheus 抓到 406 Not Acceptable - 确保 Prometheus 配置中的
metrics_path和 Gin 路由路径完全一致,比如配置里是metrics_path: "/metrics",那 Gin 就必须注册/metrics,不能是/prom/metrics
选错指标类型会让监控彻底失真
Counter 不是万能计数器,Gauge 也不能随便替代。在 Gin 中间件里混用,会导致 PromQL 查询结果完全不可信。
典型误用:
- 用
Counter记录“当前并发请求数”——Counter 只增不减,数值永远涨,根本反映不了瞬时压力 - 用
Gauge记录“总请求数”——Gauge 可增可减,但业务上总请求数天然单调递增,用 Gauge 会让 rate() 函数失效,聚合语义错乱 - 用
Summary替代Histogram统计接口耗时——Summary 在客户端计算分位数,丢失原始分布;Histogram 允许服务端灵活重算,更适合 API 监控场景 - 给
Histogram设置过窄或过宽的Buckets(如全设[0.001, 0.01, 0.1]),导致 99% 请求都落在最后一个桶里,分位数失去区分度
真实线上服务里,最稳妥组合是:CounterVec 记总请求数和错误数,HistogramVec 记耗时,Gauge 记活跃连接数或队列长度——类型和用途必须一一对应,改一个就可能影响告警逻辑。











