promhttp.handler() 返回 http.handler 而非服务器,需注册到路由再启动;labelnames 必须固定且匹配;instrumenthandlerduration 不含 tls/io 等耗时;指标须显式注册且在 listenandserve 前完成。

为什么 promhttp.Handler() 不能直接用 http.ListenAndServe() 启动?
因为 promhttp.Handler() 返回的是一个 http.Handler,不是服务器实例;它只负责响应 /metrics 请求,不启动监听。直接传给 http.ListenAndServe() 会编译报错或 panic。
- 正确做法是把它注册进路由:用
http.Handle("/metrics", promhttp.Handler()),再调用http.ListenAndServe(":8080", nil) - 如果用了
gorilla/mux或chi,必须显式挂载:r.Handle("/metrics", promhttp.Handler()).Methods("GET") - 别在主服务端口上复用同一个
http.ServeMux后又另起 goroutine 跑第二个http.Server——指标端口冲突或被覆盖很常见
自定义指标时,prometheus.NewCounterVec() 的 labelNames 为什么必须固定且不可变?
Label 名称列表在创建时就固化进指标 descriptor,后续所有 WithLabelValues() 调用都必须严格匹配长度和顺序。一旦写错,运行时报 collected metric was not registered 或 panic。
- 错误示例:
counter.WithLabelValues("login", "failed")但定义时是[]string{"action", "status", "region"}→ 少一个值,直接 panic - 建议把 label 名统一定义为常量:
const (LabelAction = "action"; LabelStatus = "status"),避免拼写不一致 - 高频打点场景下,反复调用
WithLabelValues()会分配新对象;如 label 组合有限,可提前缓存prometheus.Counter实例(比如按 status 分 3 个变量)
Web 服务暴露指标时,http_request_duration_seconds 直接用 promhttp.InstrumentHandlerDuration() 为什么不准?
它默认统计的是 handler 执行时间,不含 TLS 握手、请求体读取、响应刷出等环节;在反向代理(如 Nginx)后,真实延迟可能被掩盖,而且无法区分 slow query 或 slow template render。
- 更准的做法是手动埋点:在 handler 入口记开始时间,defer 中记录耗时并
Observe() - 注意不要在中间件里重复打点——比如 Gin 的
prometheus.WrapGin()和你自己写的 duration counter 同时启用,会导致指标翻倍 - 如果用了
net/http/pprof,确保/debug/pprof/不和/metrics共享同一套 handler,否则 pprof 的 handler 可能拦截 metrics 请求
本地开发时 curl http://localhost:8080/metrics 返回空或 404 怎么快速定位?
先确认指标是否真被注册,而不是路由没挂对。Prometheus 客户端不会自动注册指标,必须显式调用 prometheus.MustRegister()。
立即学习“go语言免费学习笔记(深入)”;
- 检查是否漏了注册:比如定义了
httpDuration = prometheus.NewHistogram(...),但没写prometheus.MustRegister(httpDuration) - 检查路径是否带前缀:有些框架(如 Echo)默认路由不处理尾部斜杠,
/metrics/会 404,而/metrics才行 - 用
prometheus.DefaultRegisterer.Gather()在代码里打印一次采集结果,看指标有没有出现在返回的*dto.MetricFamily列表中
最常被忽略的是:指标注册时机必须在 http.ListenAndServe() 之前,且不能在 handler 内动态注册——那样每次请求都会尝试重复注册,触发 panic。










