直接用 prometheus.newgaugevec 会 panic 是因未注册到默认 registerer 或指标名含非法字符(如大写字母、连字符、空格);prometheus 要求指标名仅含小写字母、数字、下划线,且须以字母或下划线开头。

为什么直接用 prometheus.NewGaugeVec 会 panic?
因为没注册到默认的 prometheus.DefaultRegisterer,或者注册时用了不兼容的命名(比如含空格、连字符、大写字母)。Go 的 Prometheus 客户端对指标名极其严格:只允许小写字母、数字、下划线,且必须以字母或下划线开头。常见错误是写成 "http_requests_total"(合法)但误写为 "HttpRequestsTotal" 或 "http-requests-total",后者在调用 Collect() 时会直接 panic。
实操建议:
- 始终用
prometheus.BuildFQName("myapp", "", "request_latency_seconds")构造全限定名,避免手写出错 - 注册前先校验:
if !prometheus.IsValidMetricName(name) { log.Fatal("invalid metric name") } - 不要复用未注册的
Collector实例——每个自定义 collector 必须显式调用prometheus.MustRegister(c)
如何实现一个带标签的自定义 collector(非 Vec)?
不是所有场景都需要 GaugeVec 或 CounterVec;有时你只想暴露一组固定标签组合的指标(如按服务版本、部署环境),且不希望每次 Set 都做 label 查找。这时应实现 prometheus.Collector 接口,手动控制 Desc 和 Metric 生成。
关键点:
Describe(ch chan 必须发送所有可能用到的 <code>*prometheus.Desc,哪怕只发一个Collect(ch chan 中每个 <code>prometheus.MustNewConstMetric必须与 Describe 发送的 Desc 类型一致(类型、label 数量、顺序)- 标签值不能为
nil或空字符串,否则采集时会被静默丢弃(Prometheus server 不报错,但目标页看不到该 series)
示例片段:
func (c *versionCollector) Collect(ch chan<h3>为什么 <code>http.Handle("/metrics", prometheus.Handler())</code> 不生效?</h3><p>因为 <code>prometheus.Handler()</code> 已在 v1.0+ 中被移除,直接调用会编译失败。新版本必须用 <code>promhttp.Handler()</code>,且需注意它默认只支持 GET,不处理 HEAD 或 OPTIONS;若你的健康检查探针发的是 HEAD,会返回 405。</p><p>实操建议:</p>
- 导入正确包:
"github.com/prometheus/client_golang/prometheus/promhttp" - 注册 handler 时加路径前缀(如
/metrics)必须与 HTTP 路由完全匹配,中间多一个斜杠(//metrics)也会 404 - 如需支持 HEAD,可包装 handler:
http.Handle("/metrics", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "HEAD" { w.WriteHeader(http.StatusOK); return }; promhttp.Handler().ServeHTTP(w, r) }))
自定义 collector 的生命周期和 goroutine 安全怎么处理?
Collector 的 Collect() 方法可能被 Prometheus server 并发调用多次(尤其当多个 scrape target 同时拉取),而你的内部状态(比如缓存的计数器、时间戳)很可能不是并发安全的。
最容易被忽略的坑:
- 用
sync.Map或sync.RWMutex保护读写共享状态,别依赖局部变量或函数内初始化的 map - 避免在
Collect()中做耗时操作(如 HTTP 请求、DB 查询)——这会导致 scrape 超时,默认 10s,超时后指标丢失且 server 记录 error - 如果必须异步更新状态,用单独 goroutine + channel 推送,
Collect()只做快照读取
复杂点在于:collector 本身没有 Start/Stop 钩子,状态初始化和销毁需由上层协调。比如你的 collector 依赖一个后台 ticker 更新值,那 ticker 的启停必须和 HTTP server 生命周期对齐,否则程序退出时 goroutine 泄漏。











