必须手动挂载 promhttp.Handler() 到 Gin 路由,否则 /metrics 返回 404;指标需包级定义并单次注册;Counter 仅增、Gauge 可增减;标签须归一化且避免高基数字段,顺序须严格匹配。

必须手动挂载 promhttp.Handler() 到路由
不注册这个 handler,curl /metrics 一定返回 404 或空响应——这是 Gin 集成里最常踩的坑。Gin 不会自动暴露指标端点,哪怕你已经调了 prometheus.MustRegister()。
正确写法是用 gin.WrapH() 包装标准 handler:
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
注意:/metrics 路径必须和 Prometheus server 的 scrape_configs.job.metrics_path 配置完全一致;如果改成了 /admin/metrics,Prometheus 抓取时也得同步改。
自定义指标必须定义为包级变量 + 单次注册
把 prometheus.NewCounterVec() 写在 handler 函数里、或在多个 init() 中重复调用 MustRegister(),会导致 panic:“duplicate metrics collector registration attempted”。
立即学习“go语言免费学习笔记(深入)”;
安全做法:
- 所有指标变量声明为包级变量(如
var httpReqCounter = prometheus.NewCounterVec(...)) - 注册逻辑只在
init()或main()开头执行一次 - 若需动态标签(如按用户 ID 统计),用
.WithLabelValues("uid_123")获取子指标,而不是新建指标
别用 promauto.NewXXX() 在热路径里反复创建——它内部会自动注册,但没做重复保护,照样 panic。
选错指标类型会导致监控失真
Counter 只能增,Gauge 才能增减。拿 Counter 当活跃连接数用,goroutine 退出时不扣减,数字就永远涨下去。
典型误用场景:
- 用
Counter记“当前在线用户数” → 应该用Gauge,并在连接断开时调.Dec() - 用
Gauge记“总请求数” → 应该用Counter,语义清晰且聚合可靠 - 用
Gauge硬凑 P99 延迟 → 应该用Histogram,并按实际延迟分布配置Buckets(比如高频接口用[]float64{0.01, 0.05, 0.1, 0.25, 0.5, 1})
标签设计不当会引发基数爆炸
每个唯一标签组合都会生成一条独立时间序列。把用户手机号、traceID、订单号直接塞进 label,几万用户一并发,指标数轻松破百万,Prometheus 存储和查询直接卡死。
合理控制维度:
- 路径中动态段必须归一化:/order/12345 → /order/{id}
- 高基数字段(如
user_id)禁用为 label;可改为按错误类型分类统计(error_type="timeout")或采样上报 - label 名必须小写+下划线,如
tenant_id,不能是TenantID或tenant-id -
CounterVec定义时的 label 顺序([]string{"method", "path", "status"})必须和.WithLabelValues("GET", "/api/v1/users", "200")传参顺序严格一致,否则 panic 或数据错位
真正难的不是写几行注册代码,而是想清楚哪些维度值得保留、哪些该聚合、哪些该丢弃——这一步错了,后面所有采集和告警都建立在流沙之上。


















