go服务暴露自定义指标的关键是指标必须在init()或main()开头显式注册到对应注册器,且promhttp.handler()需与注册器匹配;否则仅显示go_goroutines等运行时指标,业务指标如http_requests_total不会出现。

Go服务暴露自定义指标,关键不是“挂个/metrics路由”,而是指标注册时机、注册器绑定、框架适配三者必须对齐——漏掉任意一环,Prometheus抓到的就只有go_goroutines,你的http_requests_total永远不出现。
为什么Gin/Echo里/metrics返回空业务指标
常见现象:curl http://localhost:8080/metrics能看到go_memstats_alloc_bytes,但查不到自己定义的http_req_counter。根本原因是promhttp.Handler()默认只从prometheus.DefaultRegisterer读指标,而你的CounterVec没调prometheus.MustRegister(),或者注册发生在HTTP服务启动之后。
- 注册必须在
http.ListenAndServe()之前完成,推荐统一放在func init()里 - Gin不能直接用
router.GET("/metrics", gin.WrapH(promhttp.Handler()))——WrapH会丢失Content-Type协商,改用router.GET("/metrics", func(c *gin.Context) { promhttp.Handler().ServeHTTP(c.Writer, c.Request) }) - Echo要写
e.GET("/metrics", echo.WrapHandler(promhttp.Handler())),但需确认echo.WrapHandler版本兼容性(v4.10+才稳定) - GoFrame用户注意:
g.Server().BindHandler("/metrics", ghttp.NewHandlerFunc(promhttp.Handler().ServeHTTP)),别用BindObject,它不支持http.Handler接口
CounterVec注册后仍报duplicate metrics collector registration
panic不是并发问题,是同一指标名被多次注册。典型场景:多个包都import了封装指标的metrics包,每个包的init()都执行MustRegister();或用了promauto.NewCounterVec()但没指定注册器,导致不同包初始化时都往DefaultRegisterer塞同名指标。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 根治方法:所有指标定义为包级变量,注册收口到
main()函数最开头,其他模块只接收指针参数 - 临时绕过:启动前加
prometheus.Unregister(httpReqCounter)再注册,但仅限调试 - 隔离方案:用
reg := prometheus.NewRegistry()替代全局注册器,然后promhttp.HandlerFor(reg, nil)绑定路由 - go-zero用户特别注意:它的
PrometheusHandler内部已注册一套指标,你再定义同名http_requests_total必然冲突,改用CounterVec加tenant_id等业务标签区分
业务标签怎么设才不爆炸
标签值不是越细越好。/order/123456789这种带ID的路径直接当标签,每笔订单生成一个新指标序列,几万订单就打爆Prometheus内存。
- 路径动态段必须预处理:
strings.ReplaceAll(r.URL.Path, "123456789", "{id}"),统一成/order/{id} - 标签名用下划线:
tenant_id合法,tenant-id非法 -
WithLabelValues()顺序必须和NewCounterVec声明的labelNames完全一致,错一位运行时panic - 高频请求别在handler里反复调
.WithLabelValues("GET", tenantID, "200"),提前缓存counter.WithLabelValues("GET", "prod-001", "200")实例 - gRPC场景优先从
metadata.MD取X-Tenant-ID,比解析proto消息快一个数量级
goroutine退出时活跃连接数不准怎么办
用prometheus.NewCounter()统计“当前连接数”是经典错误——Counter只能增不能减,goroutine退出时没扣减,指标只涨不跌。
- 必须换
Gauge:var activeConnGauge = prometheus.NewGauge(prometheus.GaugeOpts{Name: "active_connections"}) - 连接建立时
activeConnGauge.Inc(),连接关闭时activeConnGauge.Dec() - WebSocket或长连接场景,务必确保
defer activeConnGauge.Dec()写在goroutine最外层,别被return跳过 - 如果用
net/http标准库,可监听http.Server.ConnState回调,在http.StateClosed时调Dec()
真正容易被忽略的点是:指标注册器和HTTP handler必须用同一个注册器实例。用promhttp.Handler()却把指标注册到自定义Registry,或者反过来,都会导致指标“存在却不可见”。这问题不报错,只静默丢数据,得靠curl对比/metrics输出人工核对。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










