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

Go服务暴露/metrics端点必须注册http.Handler
不手动注册 promhttp.Handler(),Prometheus抓取会返回404或空响应。默认Go的http.DefaultServeMux不会自动挂载指标路由,必须显式绑定。
常见错误是只调用prometheus.MustRegister()就以为万事大吉,但指标没暴露路径等于没做。
- 正确做法:在HTTP服务启动前,用
http.Handle("/metrics", promhttp.Handler()) - 若使用Gin,需通过
gin.WrapH(promhttp.Handler())包装;Echo则用echo.WrapHandler(promhttp.Handler()) - 路径不必非得是
/metrics,但需和Prometheus配置中的scrape_configs.job.metrics_path严格一致
自定义业务指标要避免重复注册panic
Go的Prometheus客户端对同名指标注册极其敏感:prometheus.NewCounter()后再次MustRegister()同名指标,进程直接panic,且无法recover。
典型场景是把指标定义写在handler函数里,每次请求都新建+注册;或在init()中多次执行同一段注册逻辑。
- 解决方案:所有
prometheus.NewXXX()实例必须定义为包级变量,注册只在程序启动时做一次 - 推荐结构:
var httpReqCounter = prometheus.NewCounterVec(...); func init() { prometheus.MustRegister(httpReqCounter) } - 如需动态命名(如按用户ID打标),用
CounterVec/GaugeVec,通过.WithLabelValues("uid_123")获取子指标,而非新建指标
goroutine泄漏风险:计数器不支持原子减,别用counter记录活跃连接数
业务常误用prometheus.NewCounter()统计“当前在线人数”或“活跃WebSocket连接”,但Counter只能增不能减——一旦goroutine退出未同步扣减,指标持续上涨,监控完全失真。
这是Prometheus数据模型和Go运行时行为共同导致的典型误用。
- 正确选择:
prometheus.NewGauge(),它支持.Inc()、.Dec()、.Set() - 务必确保每个goroutine退出前调用
gauge.Dec(),建议用defer gauge.Dec()包裹 - 若goroutine可能panic,需配合
recover()确保Dec执行,或改用带context取消的连接管理逻辑
本地调试时curl/metrics返回空?检查指标是否被GC回收
如果指标变量定义在函数内、未被任何全局引用持有,Go编译器可能在注册后将其当作无用对象回收,导致/metrics返回空内容,且无任何报错。
这种问题在快速原型或测试代码中最容易出现,现象诡异,排查耗时。
- 确认所有
prometheus.NewXXX()返回值被赋给包级变量(非局部变量) - 用
go tool compile -gcflags="-m" main.go检查变量逃逸情况,若提示“moved to heap”仍不够,必须有强引用 - 最简验证:在main函数末尾加
fmt.Printf("%v", yourGauge),确保变量存活到进程结束
curl -s localhost:8080/metrics | grep my_app确认文本输出里有你的指标行,再进Prometheus界面查曲线。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











