go程序暴露自定义metrics给prometheus抓取的唯一稳妥方式是用prometheus/client_golang注册指标并挂载http.handle("/metrics", promhttp.handler());手写响应、动态注册或绕过mustregister()均会导致运行时panic。

用 prometheus/client_golang 注册指标并挂载 http.Handle("/metrics", promhttp.Handler()),是唯一稳妥的暴露方式;手写响应、动态注册、绕过 MustRegister() 都会在上线后立刻出问题。
注册必须在启动时完成,且只能一次
重复调用 prometheus.MustRegister() 会 panic:duplicate metrics collector registration attempted。这不是警告,是运行时崩溃。
- 所有指标(
NewCounterVec、NewHistogram等)必须在init()或main()开头一次性注册,不能放在 HTTP handler、中间件、goroutine 或循环里 - 多个包都定义同名指标变量(比如两个
var httpRequestTotal = prometheus.NewCounterVec(...)),会导致各自注册 → 冲突。应统一声明为全局变量,只在一个地方初始化+注册 - 测试中若多次调用
main()(如多个 test main 函数),需确保指标注册逻辑不被执行两次;可加sync.Once包裹注册逻辑,或改用prometheus.Register()(失败不 panic)做防御
选错指标类型会让 PromQL 直接失效
Counter 和 Gauge 不是“差不多能用”,而是语义完全不可互换:Prometheus 的 rate()、increase() 只对 Counter 有效;avg_over_time() 对 Gauge 合理,对 Counter 就是噪音。
- 累计量(请求总数、错误总数)→ 用
Counter;服务重启后归零是正常行为,rate()能自动处理跳变 - 瞬时值(当前活跃连接数、缓存大小、队列长度)→ 用
Gauge;它支持Set()、Add(),但绝不能用Inc()模拟计数 - 延迟类指标(HTTP 响应时间)→ 必须用
Histogram或Summary;用Counter记耗时毫秒数毫无意义,Observe(123.5)才是正确姿势
带标签的指标(Vec 类型)必须严格校验 label 值
CounterVec、GaugeVec 的 WithLabelValues("GET", "200") 如果传入非法值(空字符串、含特殊字符、label 数量不对),会 panic:inconsistent label cardinality,且该 panic 无法被 handler 的 recover 捕获(发生在指标内部 map 查找阶段)。
- 业务代码中拿到 label 值(如
r.URL.Query().Get("channel"))后,必须先校验是否为空、是否在白名单内,再传给WithLabelValues() - 高频打点场景(如每秒数千次)避免反复调用
WithLabelValues();可预先用WithLabelValues("POST", "500")缓存子指标对象,直接调.Inc() -
ConstLabels只用于进程级静态属性(如version、instance),绝不塞请求级动态值(如user_id),否则 label 组合爆炸,Prometheus 内存直接飙高
/metrics 返回 404 或空内容的三大硬坑
不是代码没写对,而是三个基础环节断了链路:指标没注册 → promhttp.Handler() 查不到;没挂到路由 → 请求根本进不来;挂错注册器 → 默认 Handler 还是查默认注册器。
- 确认是否用了自定义
prometheus.NewRegistry():如果用了,http.Handle("/metrics", promhttp.Handler())仍查默认注册器,必须改用promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) - 检查是否误用
http.HandleFunc()(函数式)而非http.Handle()(Handler 接口式);后者才能正确透传promhttp.Handler的响应头(尤其是Content-Type: text/plain; version=0.0.4) - 别在
/metrics上加中间件(鉴权、日志、CORS);Prometheus 抓取时不带 token、不走浏览器流程,中间件一拦就 401/403;真要保护,应在反向代理层(Nginx、ALB)做
最易被忽略的是指标命名和 label 设计:名称必须全小写+下划线(http_request_total),不能有空格或短横线;label 维度一旦上线就几乎无法删除,加之前要想清楚基数是否可控——一个未泛化的 path="/user/123" 就可能生成上万条时间序列。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











