/metrics返回空或无自定义指标的根本原因是指标未注册且未打点:必须用prometheus.mustregister()显式注册countervec等指标,并调用.inc()或.observe()首次触发,否则不会出现在/metrics中。

为什么 /metrics 返回空或看不到自定义指标
根本不是代码没跑,是指标没“活过来”:既没注册进注册器,也没被调用过 .Inc() 或 .Observe()。
prometheus.NewCounterVec() 只是创建一个 Go 对象,必须显式调用 prometheus.MustRegister(yourMetric) 才能进默认注册表;若用了独立 prometheus.NewRegistry(),就得配 promhttp.HandlerFor(reg, )。
- 刚启动时
http_requests_total在/metrics里就是空的,正常——只有某次请求真正执行了httpRequestsTotal.WithLabelValues("GET", "/api/v1", "200").Inc(),它才会首次出现 - label 值不能为空字符串或
nil;声明了三个 label(如[]string{"method", "endpoint", "status"}),调用时就必须传三个非空字符串,顺序、数量、类型全得对,错一个就静默丢失——/metrics里查不到,也无 panic 或日志 - 别在 HTTP handler 里动态新建指标再注册——这是高频 panic 来源,
duplicate metrics collector registration attempted就是这么来的
如何正确暴露 /metrics 端点且不被业务拖垮
别把监控端点塞进业务 HTTP Server 里,否则业务 panic 或中间件改响应头会直接让监控失效。
起一个独立 http.Server,监听如 :9091,绑定空地址(即 ":9091",不是 "127.0.0.1:9091"),确保容器外或宿主机上的 Prometheus 能访问到。
- handler 必须直挂
promhttp.Handler()或promhttp.HandlerFor(reg, ),禁用任何中间件拦截——它需要原生Content-Type: text/plain; version=0.0.4,加个 gzip 或Content-Type: text/html都会导致 Prometheus 抓取失败 - 务必注册运行时指标:
prometheus.MustRegister(prometheus.NewGoCollector()),否则go_goroutines、go_memstats_alloc_bytes这类基础指标压根不会出现 - 如果服务用了 Gin,用
router.GET("/metrics", gin.WrapH(promhttp.Handler()))是可行的,但要确认路由路径没被其他中间件劫持或重写
HTTP 请求埋点该用什么指标类型和 label 设计
用错类型或 label 维度爆炸,等于白埋——查不到数据、存储崩、查询慢都是后遗症。
- 请求总数用
CounterVec,响应时间必须用HistogramVec:前者支持rate(),后者才能算 P95/P99;别用Gauge记总请求数,也别用Counter记耗时 - 路径 label 别用
r.URL.Path(如/user/123),要提取路由模板(Gin 用c.FullPath(),Echo 用c.Request().URL.Path配正则归一化) - 避免高基数标签:用户 ID、手机号、traceID 等字段别直接塞进 label,改用
Counter按error_type分类或采样上报 -
label名必须全小写、下划线分隔(如tenant_id),不能写tenant-id或TenantID
多实例或热重载场景下怎么避免注册冲突
复用 prometheus.DefaultRegisterer 在多实例共存、插件热加载、单元测试反复运行时极易触发 panic。
核心原则:注册控制权收归主应用,不依赖全局隐式状态。
- 统一用
reg := prometheus.NewRegistry()+prometheus.NewCounterVec(...)+reg.MustRegister() - 暴露端点时必须用
promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),不能用默认promhttp.Handler() -
promauto在单包小项目里够用,但一旦跨包(比如中间件、子模块各自初始化同名指标),就会因重复注册 panic;它的With()也不能解决命名隔离问题 - 单元测试中每次都要新建独立 registry,避免污染全局状态;测试完不清理,下次
go test -run TestA -run TestB就可能 fail
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











