必须在http.ListenAndServe()之前完成指标注册,否则/metrics返回200但无业务指标;根本原因是Prometheus只暴露已注册的collector,未调用prometheus.MustRegister()的指标不会出现,运行时指标也需显式注册NewGoCollector()。

必须在 http.ListenAndServe() 之前完成指标注册,否则 /metrics 返回空或 200 但无数据——这是 90% 的“监控没生效”问题根源。
为什么 /metrics 返回 200 却看不到指标?
不是网络不通,也不是 Prometheus 配置错,而是指标根本没注册进默认注册器。Prometheus 不扫描变量,只读取已注册的 collector。
-
promhttp.Handler()只暴露当前注册器里“活着”的指标,未调用prometheus.MustRegister()的变量等于不存在 - Go 运行时指标(如
go_goroutines、go_memstats_alloc_bytes)默认不启用,必须显式注册:prometheus.MustRegister(prometheus.NewGoCollector()) - 自定义指标(如
http_requests_total)不能在 handler 里反复NewCounterVec+MustRegister,会 panic 报duplicate metrics collector registration attempted - 若用了
prometheus.NewRegistry()自定义注册器,promhttp.Handler()查不到它——得换promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
用 NewCounterVec 还是 NewCounter?
HTTP 类业务监控几乎全用 NewCounterVec,裸 NewCounter 基本没实用价值。
-
NewCounter没 label,所有请求堆在一个时间序列里,rate(http_requests_total[5m])算出来只是个笼统总数,无法按method或status下钻分析 -
NewCounterVec定义时传[]string{"method", "path", "status"},调用时必须严格按顺序、非空、类型一致:.WithLabelValues("GET", "/api/user", "200").Inc() - 传空字符串
""、少一个值、顺序错、或用nil,指标会静默丢失——/metrics里查不到,也不报错 - label 值来自路径参数、用户 ID、trace ID 等动态字段时,极易引发高基数(high cardinality),单个指标生成成千上万个时间序列,吃光 Prometheus 内存
如何在 Gin/Echo 等 Web 框架中挂载 /metrics?
不能直接用 http.Handle(),必须适配框架生命周期,避免被中间件拦截或重写状态码。
立即学习“go语言免费学习笔记(深入)”;
- Gin 示例:
router.GET("/metrics", func(c *gin.Context) { promhttp.Handler().ServeHTTP(c.Writer, c.Request) }) - Echo 示例:
e.GET("/metrics", echo.WrapHandler(promhttp.Handler())) - 务必加基础鉴权,比如用
BasicAuth中间件限制仅内网或运维账号访问 - 禁用
OPTIONS等非必要方法,防止被恶意探测;不要将/metrics暴露在公网
注册时机和位置最容易被忽略
注册不是“写完指标再补上”,而是在程序启动最早期执行——init() 函数或 main() 开头,且必须在 http.ListenAndServe() 之前。
- 运行时指标、自定义 CounterVec/Histogram/Gauge 全部要在这一步注册完毕
- 如果服务启用了热重载或配置中心,指标注册仍只能做一次;后续 reload 不能重复注册同名指标
- 使用
promauto包(如promauto.NewCounter)可自动注册,但依然要求包初始化早于 HTTP server 启动


















