Int64Counter 初始化失败或 Add() panic,主因是重复创建同名指标实例导致返回 nil 后未检错即调用;必须包级初始化、指定单位、避免类型混用,并确保 MeterProvider 和导出器正确注册。

Int64Counter 初始化失败或后续 Add() panic,基本是因为复用同名指标实例
Go SDK 对 metric 实例的命名和类型校验非常严格:同一个 Meter 下,meter.Int64Counter("http.requests") 只能成功调用一次。第二次调用会返回 nil, error,但很多人忽略 err 检查,直接对 nil 调用 .Add(),导致 panic。
- 必须在包级或
init()中初始化,而不是每次 handler 或函数里重复创建 - 初始化时务必传入
instrument.WithUnit("1")(单位不可省略,否则部分导出器静默丢弃) - 推荐写法:
var httpRequests = meter.NewInt64Counter("http.requests", metric.WithUnit("1")) - 如果指标名拼错、单位不一致、类型混用(比如先用
Int64Counter,后又试Int64UpDownCounter),都会触发重复注册错误
Int64UpDownCounter 和 Int64Counter 语义不能互换
选错类型不是“数据不准”,而是直接崩溃或静默失效:
立即学习“go语言免费学习笔记(深入)”;
-
Int64Counter只接受非负值;传-1会 panic:"counter cannot decrease" -
Int64UpDownCounter允许负值,但它是“变化量”,不是“当前值”——.Add(-1)表示减少 1,不是设为 -1 - 活跃连接数、goroutine 数、内存字节数这类瞬时状态,不能靠 updown counter 累加估算,应搭配
Int64Gauge+instrument.WithCallback定期读取真实值
Histogram 的 Boundaries 必须创建时固定,运行时无法调整
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
延迟类指标(如 HTTP 请求耗时)常用 Histogram,但它的分桶边界一设就定死:
- 创建时必须显式传
metric.WithExplicitBoundaries([]float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 5}) - 边界太窄(比如只到 100ms)会导致 99% 的请求全挤进最后一个桶,P99 统计完全失真
- 边界太宽(比如最大设到 60s)会让 P50/P90 区分度极差,看不出毫秒级差异
- 不要照搬 Prometheus client 的默认边界;OpenTelemetry 的
Histogram默认不带任何边界,必须手动指定
prometheus.Exporter 和已有 promauto 冲突,指标可能重复或丢失
如果你项目里已经在用 prometheus/client_golang 的 promauto(比如 promauto.NewCounter),再引入 OTel 的 prometheus.Exporter 是危险操作:
- 两者注册机制不同:
promauto直接往prometheus.DefaultRegisterer注册;OTel 的 exporter 需要你显式传入同一个Registerer实例 - 混用时常见现象:/metrics 返回重复指标(如两个
http_requests_total)、部分指标不出现、甚至 panic 报 “duplicate metrics collector registration” - 解决办法:要么全切 OTel +
prometheus.Exporter,要么保留原生 client,用 OTel 的otlphttp.Exporter直连 Collector,不要共存
指标没出现在 /metrics 或 Collector 端,大概率是导出器没注册或上下文没传对
本地调试时看不到数据,不是代码写错了,而是链路断在某个环节:
-
MeterProvider必须在首次调用meter.Meter()前完成配置;如果包级变量初始化顺序错乱,meter可能绑定到空 provider -
prometheus.Exporter必须用WithRegisterer(prometheus.DefaultRegisterer)显式绑定,否则指标不会暴露在/metrics - HTTP handler 里调
counter.Add()时,必须传入带 span 的context(比如从trace.SpanFromContext(r.Context())提取),否则指标脱离 trace 上下文,部分 backend 会丢弃
边界、单位、注册器、上下文——这四个点漏掉任何一个,指标就悄无声息地消失,连 error 都不报。

















