promhttp.handler()不能直接用于gin/echo路由,因其返回http.handler而非框架要求的上下文函数;需用gin.wraph或echo.wraphandler包装,并启用recovery中间件。

为什么 promhttp.Handler() 不能直接用在 Gin 或 Echo 的路由里
因为 promhttp.Handler() 返回的是标准 http.Handler,而 Gin/Echo 的 GET 方法只接受函数签名形如 func(c *gin.Context) 或 func(e echo.Context) error 的处理函数。直接传入会编译报错或 panic。
正确做法是包装一层适配器:
- Gin 场景下用
gin.WrapH(promhttp.Handler()) - Echo 场景下用
echo.WrapHandler(promhttp.Handler()) - 别漏掉
router.Use(middleware.Recovery())—— 否则指标 handler 崩溃会导致整个 /metrics 500
自定义 Counter 和 Gauge 时,label 值为空会引发什么问题
空 label 值(比如 status="")会让 Prometheus 拒绝采集,日志里出现 invalid metric family name 或 duplicate sample for timestamp 类错误;更隐蔽的是,同一指标多次调用 WithLabelValues("") 实际上会注册多个匿名时间序列,造成 cardinality 爆炸。
务必做前置校验:
- 用
strings.TrimSpace()清洗 label 输入 - 对关键 label(如
user_id、endpoint)设默认值,例如unknown而非空字符串 - 在
Inc()或Set()前加if val == "" { val = "unknown" }
在 Golang HTTP 中间件里暴露请求延迟,Observe() 的时机很关键
延迟直方图(Histogram)必须在请求真正结束、响应已写出后才能调用 Observe(),否则测到的是中间件耗时,不是端到端延迟。常见错误是在 next(c) 前就记录,或者没考虑 panic 恢复路径。
安全写法要覆盖三类出口:
- 正常返回:在
next(c)后立刻histogram.WithLabelValues(status).Observe(elapsed.Seconds()) - panic 恢复:用
defer+recover()捕获,并标记 status 为500 - 超时/中断:检查
c.Request.Context().Err()是否为context.DeadlineExceeded或context.Canceled,对应设 status 为timeout或canceled
prometheus.MustRegister() 在热重载场景下会 panic
如果服务支持配置热更新(比如监听文件变更重新加载 metrics),重复调用 MustRegister() 会触发 duplicate metrics collector registration attempted panic。这不是 bug,而是 Prometheus 的设计约束:每个 collector 只能注册一次。
稳妥方案只有两个:
- 启动时一次性注册全部指标,后续只更新 label 值(
Set()/Inc()),不新增 collector - 若真需动态增删指标(如按租户隔离),改用
prometheus.NewRegistry()自建 registry,再通过promhttp.HandlerFor(reg, promhttp.HandlerOpts{})暴露,避免和全局 registry 冲突
多数业务场景其实不需要动态注册——把指标维度设计好,用 label 区分就够了。强行热注册反而让 Cardinality 难控、Prometheus 查询变慢。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











