fix(metrics): duplicate metrics collector registration panic(线上 CrashLoopBackOff)

=== 现象 ===
上一提交 b49677f 部署后平台直接 CrashLoopBackOff(5 次重启):
    Initializing database...
    panic: duplicate metrics collector registration attempted
    lokistack/pkg/metrics.Register.func1()  metrics.go:125

=== 根因 ===
prometheus/client_golang 在 init() 里已经把 GoCollector 与 ProcessCollector
注册进 **默认 registry**(prometheus.DefaultRegisterer)。我又对同一默认 registry
调用 MustRegister 注册了同一批 collector → 重复注册 → 运行时 panic。

致命点:go build / go vet / 普通单测**全都发现不了** ——
MustRegister 的重复检查只在运行期执行,只有真的调用 Register() 才会炸。
所以整个 P0 打磨在测试阶段是"全绿"的,一上线就挂。

=== 修法 ===
不再碰默认 registry,改用自有实例:

    var registry = prometheus.NewRegistry()      // 独立,不受 init() 影响
    registry.MustRegister(...)                    // 注册全部指标
    promhttp.HandlerFor(registry, ...)            // 抓取读自有 registry

好处:完全掌控注册内容,未来再加 collector 不会与库的默认行为冲突。

=== 回归测试(这才是关键收获)===
新增 backend/pkg/metrics/metrics_test.go:
  - TestRegisterIsIdempotent   : 连续调用 Register 两次,必须不 panic
  - TestHandlerExposesCustomMetrics : 断言抓取输出含 lokistack_* 与 go_goroutines
  - TestRegisterConcurrent     : 20 个 goroutine 并发调用不 panic
这三条在修复前必然失败,修复后全绿。以后再改指标定义会被它们拦住。

镜像:lghost/lokistack:latest + v0.1 = 2f8f3c85643e
This commit is contained in:
opsx committed 2026-10-28 21:32:36 +00:00
1 parent b49677f6a6
commit c44ffad61f
2 files changed
+79 -3

No files matched your search

+12 -3
View File
@@ -116,13 +116,20 @@ var (
[]string{"version", "go_version"},
)
// registry 用独立实例,不用 prometheus.DefaultRegisterer。
// 原因:client_golang 在 init() 里已经把 GoCollector / ProcessCollector
// 注册进默认 registry,再用 MustRegister 注册同一批 collector 会
// **运行时 panic: duplicate metrics collector registration attempted**
// (编译期和单测都发现不了,只在进程启动时炸)。
registry = prometheus.NewRegistry()
once sync.Once
)
// Register 注册全部指标(幂等)。version 一般传 main.version。
func Register(version string) {
once.Do(func() {
prometheus.MustRegister(
registry.MustRegister(
HTTPRequestsTotal, HTTPRequestDuration, HTTPInFlight,
DBSizeBytes, DBSaveDuration, DBSaveTotal, DBRecordsTotal,
DeploymentsTotal, CollectorsTotal,
@@ -135,8 +142,10 @@ func Register(version string) {
})
}
// Handler 返回 Prometheus 抓取端点。
func Handler() http.Handler { return promhttp.Handler() }
// Handler 返回 Prometheus 抓取端点(读自有 registry,不含默认 registry 的重复项)。
func Handler() http.Handler {
return promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
}
// ── 埋点辅助(各层直接调) ──