Go服务暴露/metrics端点必须显式注册promhttp.Handler(),否则返回404或空响应;自定义指标需包级定义、仅启动时注册一次;计数器不可用于活跃连接统计,应改用Gauge并确保goroutine退出时调用Dec()。

Go服务暴露/metrics端点必须显式绑定promhttp.Handler()
不手动挂载promhttp.Handler(),Prometheus 抓取永远返回 404 或空响应。Go 的 http.DefaultServeMux 不会自动识别 /metrics 路径,哪怕你已调用 prometheus.MustRegister() 注册了所有指标。
常见错误是只写 http.Handle("/metrics", promhttp.Handler()) 却没启动 HTTP 服务,或把这行放在 http.ListenAndServe() 之后——顺序错导致 handler 未生效。
- 务必在
http.ListenAndServe()之前注册:先http.Handle("/metrics", promhttp.Handler()),再http.ListenAndServe(":8080", nil) - 若用 Gin,不能直接
router.GET("/metrics", ...)返回字符串,必须用gin.WrapH(promhttp.Handler())包装,否则 Accept 头协商失败,可能返回 406 - 路径名不必强制为
/metrics,但 Prometheus 配置里的scrape_configs.metrics_path必须与之完全一致(包括开头的/)
Gin中间件里打点要提取真实业务标签,不是只套method/status
默认中间件统计的 http_requests_total{method="GET",status="200"} 在多租户或微服务场景下毫无区分度——所有租户的订单创建失败都挤在同一时间线里,根本无法定位问题来源。
业务维度漏掉,后续 PromQL 就没法 by(tenant_id) 聚合,告警规则也写不准。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 从请求上下文或 header 提取
X-Tenant-ID、X-Service-Name等字段,传入指标打点逻辑 - 定义
CounterVec时显式声明业务标签:[]string{"method", "path", "status", "tenant_id"},顺序必须和.WithLabelValues("GET", "/api/order", "200", "prod-001")完全一致 - 路径中动态 ID(如
/order/12345)必须预处理为占位符(/order/{id}),否则每个 ID 都生成新指标序列,导致高基数爆炸 - 避免在 handler 内反复调用
.WithLabelValues(),应提前缓存子指标实例,例如counterByTenant["prod-001"].Inc()
自定义指标必须包级定义+单次注册,否则panic或指标丢失
Go 的 Prometheus 客户端对同名指标注册极其敏感:prometheus.MustRegister() 重复执行直接 panic,且无法 recover;漏注册则 /metrics 里永远看不到你的业务指标。
典型错误是把 prometheus.NewCounterVec() 写在 handler 函数里,每次请求都新建+注册;或多个包各自 init() 里调同一段注册逻辑。
- 所有指标变量必须定义为包级变量:
var httpReqCounter = prometheus.NewCounterVec(...) - 注册只做一次,在
init()或main()开头:prometheus.MustRegister(httpReqCounter) - 若用了
promauto,它内部会 fallback 到默认注册器,但前提是没被其他包提前触发duplicate metrics collector registrationpanic - 调试时可临时加
prometheus.Unregister(httpReqCounter)再注册,或改用prometheus.NewRegistry()隔离,避免污染全局
计数器不能用于活跃连接数,Gauge才是正确选择
用 Counter 统计“当前在线用户数”或“活跃 WebSocket 连接”,看似简单,实则埋雷——Counter 只能增不能减,goroutine 退出时若没同步扣减,指标持续上涨,监控完全失真。
这种误用在压测后尤其明显:连接断开,但指标卡在高位不动,告警反复误触发。
- 活跃连接数、任务队列长度、缓存命中率等瞬时状态量,必须用
Gauge -
Gauge支持.Set()、.Add()、.Dec(),goroutine 退出前务必调用.Dec() - 别依赖 GC 清理——goroutine 崩溃或 panic 时不会自动调
Dec(),需配合 defer 或 context.Done() 显式清理 - 延迟类指标必须用
Histogram,而非Counter记总耗时——只有Histogram才能算出 P95/P99,Counter算不出分布
最易被忽略的是:Go 进程重启后,所有 Counter 和 Histogram._sum/_count 都归零,rate() 在头几个采集周期会计算异常,可能误触发延迟告警。生产环境建议搭配 process_start_time_seconds 辅助判断是否刚重启。

















