指标收集器必须实现 prometheus.collector 接口,否则无法被 prometheus registry 识别和采集;这是硬性契约而非可选功能,且必须完整实现 describe 和 collect 方法。

指标收集器必须实现 prometheus.Collector 接口
不实现这个接口,你的模块就无法被 Prometheus 的 Registry 识别和采集。它不是可选的“增强功能”,而是硬性契约——只有满足 Describe(<code>chan) 和 Collect(<code>chan) 两个方法,才算真正接入生态。
常见错误是只暴露一个 GetMetrics() 方法返回 []prometheus.Metric,结果在 prometheus.MustRegister() 时 panic:「collector is not a Collector」。这不是类型断言失败,而是根本没实现接口。
- 必须用指针接收者实现接口(值接收者会导致接口不匹配)
-
Describe必须发送所有可能生成的*prometheus.Desc,哪怕当前无数据;不能漏发、不能动态增减描述符集合 -
Collect中每条prometheus.Metric的Desc()返回值,必须与Describe中某条*prometheus.Desc完全一致(包括 name、help、constLabels、variableLabels)
用 prometheus.NewGaugeVec 或 NewCounterVec 封装内部状态
别手动 new struct + 实现 prometheus.Metric——既易出错又难维护。标准向量类型(GaugeVec、CounterVec 等)已内置线程安全、label 校验、Desc 一致性保证,且与 Collect() 生命周期天然对齐。
典型误用是把 GaugeVec 当作普通 map 存储,然后在 Collect 里遍历调用 .WithLabelValues() 并 send。这没问题,但要注意:
- 所有 label keys 必须在初始化时通过
prometheus.Labels{}固定声明,运行时不能新增 key -
WithLabelValues()返回的prometheus.Gauge是可直接Set()/Inc()的,无需再包装 - 如果指标生命周期短(如 per-request 计数),优先用
prometheus.NewCounterVec+DefBuckets配合直方图,而非反复创建销毁 Gauge
注册器隔离:避免全局 prometheus.DefaultRegisterer
模块对外暴露的唯一注册入口应该是 Register(registry prometheus.Registerer) 方法,而不是直接调用 prometheus.MustRegister()。后者会污染全局 registry,导致测试难 mock、多实例冲突、init 循环依赖。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
真实场景中,用户可能同时加载多个版本的你的模块,或在不同 namespace 下运行隔离实例。这时:
- 构造函数应接受
prometheus.Registerer(通常是*prometheus.Registry),并在内部保存引用 -
Register()方法仅负责将自身 collector 注册到该 registerer,不执行任何副作用 - 测试时可传入
prometheus.NewPedanticRegistry(),它会在注册/采集阶段严格校验 Desc 一致性,提前暴露 bug
暴露 HTTP handler 时别硬编码 /metrics
模块不应自行启动 HTTP server 或绑定路径。正确做法是提供 Handler() http.Handler 方法,由使用者决定挂载位置(/metrics、/admin/metrics、甚至嵌入 gin echo 路由)。
常见陷阱是直接写 http.Handle("/metrics", promhttp.Handler()),这导致:
- 无法与现有 mux 冲突(比如主服务已用 gorilla/mux,你却用 net/http 默认 mux)
- 无法设置中间件(鉴权、日志、超时)
- 无法复用 TLS 配置或 Host 匹配规则
示例:用户代码只需 r.Handle("/custom-path", yourCollector.Handler()).Methods("GET"),其余交由框架处理。
最难调试的其实是 label 值的合法性——空字符串、含斜杠、非 ASCII 字符都会让 WithLabelValues() panic,而错误堆栈不指向你的业务代码。建议在 set 前加一层 prometheus.LabelValue 校验,或用 strings.ReplaceAll 清洗输入。这不是过度设计,是上线后第一类高频告警来源。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










