需显式将自定义指标注册到同一 prometheus.registry 实例,并确保该实例传给 promhttp.handlerfor;指标类型须按语义选用:counter 仅用于单调递增累计值,gauge 用于可增减瞬时值,histogram 用于需分位数分析的分布型观测值。

怎么注册自定义指标到 Prometheus Registry
Go 的 prometheus.NewRegistry() 默认不自动接入全局 registry,直接调用 prometheus.MustRegister() 会往默认 registry 写,但你的 HTTP handler 如果用的是自定义 registry(比如 http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))),指标就对不上——查不到、抓不到、Prometheus 显示空。
实操建议:
- 统一用一个 registry 实例,显式传给 handler 和所有
MustRegister()调用 - 别混用
prometheus.MustRegister()(默认 registry)和reg.MustRegister()(自定义 registry) - 初始化顺序要早于 handler 注册:先建 registry,再注册指标,最后挂 handler
示例关键片段:
reg := prometheus.NewRegistry()
counter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "my_app_requests_total",
Help: "Total number of requests.",
},
[]string{"method", "status"},
)
reg.MustRegister(counter) // 注意:是 reg.MustRegister,不是 prometheus.MustRegister
<p>http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
Counter / Gauge / Histogram 怎么选,常见误用在哪
选错指标类型会导致查询语义错误或聚合失效。比如把瞬时内存用量当 Counter 累加,结果值越查越大;或者把请求耗时用 Gauge 记录,丢掉分布信息,无法算 P95。
立即学习“go语言免费学习笔记(深入)”;
判断依据只看数据本质:
-
Counter:只增不减的累计值(如总请求数、总错误数)。不能用它存“当前活跃连接数” -
Gauge:可升可降的瞬时快照(如内存使用量、goroutine 数、温度) -
Histogram:需统计分布的观测值(如 HTTP 延迟、数据库查询耗时)。注意它自带_sum、_count、_bucket,别自己再记 sum
容易踩的坑:
- 用
Gauge替代Histogram省事,结果没法做分位数计算 - 在 goroutine 里反复
Observe()同一个Histogram实例没问题,但别在每次 HTTP 请求里 new 一个新 histogram -
Summary虽然也支持分位数,但不支持服务端聚合,多实例场景下不如Histogram
如何安全暴露业务状态,避免阻塞 / panic 影响 metrics endpoint
Exporter 的 /metrics 接口被 Prometheus 频繁轮询,如果采集逻辑里有网络请求、锁竞争、或未处理的 panic,整个 endpoint 就会超时甚至 crash,导致所有指标中断。
核心原则:采集逻辑必须快、无副作用、失败可降级。
- 所有业务状态读取用 copy-on-read,别在采集时现场查 DB 或发 HTTP
- 用
sync.RWMutex保护共享状态,采集时只读(RLock),更新时才写(Lock) - 在
Collect()方法里 recover panic,记录日志但不让它冒泡到 handler - 考虑加采集超时:用
context.WithTimeout包裹慢操作,超时则跳过该指标或填默认值
示例片段(带 panic 捕获):
func (e *MyCollector) Collect(ch chan<- prometheus.Metric) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in Collect: %v", r)
}
}()
// ... 正常采集逻辑
}
为什么本地跑通了,上线后 Prometheus 抓不到指标
最常见原因是监听地址绑定错了:http.ListenAndServe(":8080", nil) 在容器或 systemd 里可能绑定了 127.0.0.1:8080,而 Prometheus 从宿主机或另一容器访问时连不上。
检查和修复点:
- 启动时打印监听地址,确认是
0.0.0.0:8080而非127.0.0.1:8080 - Docker 运行时加
-p 8080:8080,且 Go 代码里用":8080"(不是"127.0.0.1:8080") - Kubernetes 中 Service 的
targetPort要和容器监听端口一致,Pod readiness probe 最好也指向/metrics - Prometheus 配置里的
targets地址要能 DNS 解析 + 网络可达,用curl -v http://pod-ip:8080/metrics手动验证
另一个隐蔽问题是 Content-Type:确保 handler 返回的是 text/plain; version=0.0.4; charset=utf-8,否则 Prometheus 会静默跳过——这由 promhttp.HandlerFor 自动设置,别自己覆盖 header。










