必须将指标声明为包级变量且只注册一次,如var httprequeststotal = prometheus.newcountervec(...)并在init()或main()中调用prometheus.mustregister;gin需显式挂载promhttp.handler();标签顺序、命名规范及counter/gauge语义须严格遵循prometheus最佳实践。

指标必须声明为包级变量且只注册一次
你的 httpRequestsTotal 出现在 /metrics 里,还是根本没影?大概率是定义在 handler 里、每次请求都 NewCounterVec + MustRegister。这会直接 panic:duplicate metrics collector registration,而且无法 recover。
正确做法只有两条:
-
var httpRequestsTotal = prometheus.NewCounterVec(...)必须写在包顶层(不是函数里),作为包级变量 - 注册动作统一放在
init()或main()开头,只执行一次:prometheus.MustRegister(httpRequestsTotal) - 如果用了多个 metrics 文件,确保只 import 一次;重复 import 同一个 metrics 包,等于重复调用
MustRegister
Gin 路由必须显式挂载 promhttp.Handler()
访问 /metrics 返回 404 或空内容?不是 Prometheus 配置错了,是你没把 handler 挂到 Gin 路由上。
promhttp.Handler() 只暴露默认注册器里的指标,而 Gin 默认不接管这个路径。必须手动绑定:
- Gin 写法:
router.GET("/metrics", gin.WrapH(promhttp.Handler())) - 别写成
router.GET("/metrics", func(c *gin.Context) { c.Data(200, "text/plain", ... ) })—— 这会丢掉 Accept 头协商和 gzip 压缩,甚至返回406 Not Acceptable - 如果你用了自定义注册器(比如
reg := prometheus.NewRegistry()),必须用promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),否则指标不会出现
标签名和 WithLabelValues() 顺序必须严格一致
定义 NewCounterVec 时传的标签数组是 []string{"method", "status", "tenant_id"},那后续打点时 .WithLabelValues("GET", "200", "prod") 的顺序就得一模一样。错一位,运行时 panic:inconsistent label cardinality。
几个硬约束:
- 标签名只能是小写字母、数字、下划线,不能以数字开头,不能含短横线(
pay-channel❌,pay_channel✅) - 值里别塞高基数字段,比如
user_id或完整 URL —— 会导致 Prometheus series 数暴涨,存储和查询全崩 -
ConstLabels只用于静态属性(如version、instance),别往里塞请求级动态值
Counter 和 Gauge 别混用,HPA 场景尤其要小心
用 Counter 记“当前活跃连接数”?这是典型误用。Counter 是累计值,只增不减;活跃连接数是瞬时状态,该用 Gauge。
更隐蔽的问题在 HPA:K8s Horizontal Pod Autoscaler 需要的是速率,不是原始 Counter 值。你在 Go 里定义了 my_app_http_requests_total,Adapter 的 metricQuery 必须写成 rate(my_app_http_requests_total[2m]),而不是裸指标名。否则 HPA 看到的是 127489 这种不断增长的大整数,完全无法决策。
直方图(Histogram)也容易踩坑:Buckets 设置不合理(比如全设成 100ms 以下),真实延迟超限后所有数据都堆在 +Inf bucket,分位数失去意义。











