/metrics返回空或仅有go_*指标,是因为未显式注册自定义业务指标:promhttp.handler()仅暴露默认注册表中的go运行时指标;必须用prometheus.newregistry()创建新注册表,将countervec等业务指标通过reg.mustregister()显式注册,并用promhttp.handlerfor(reg, ...)挂载。

Go 应用暴露 Prometheus 指标本身不难,但真正落地时,90% 的问题出在指标注册时机、标签滥用、Histogram 分桶设置不当,以及没意识到 promhttp.Handler() 默认只暴露 Go 运行时指标——业务指标不会自动出现。
为什么 /metrics 返回空或只有 go_* 指标?
这是新手最常踩的坑:直接用了 promhttp.Handler(),以为它会自动收集所有指标。其实它只绑定默认注册表(prometheus.DefaultRegisterer),而你自定义的 Counter、Histogram 如果没显式注册进去,就根本不会被采集。
- 错误写法:
http.Handle("/metrics", promhttp.Handler())—— 只暴露go_goroutines、go_memstats_alloc_bytes等内置指标 - 正确做法:创建新注册表,把业务指标注册进去,再传给
promhttp.HandlerFor() - 示例关键片段:
reg := prometheus.NewRegistry()<br>reqCounter := prometheus.NewCounterVec(<br> prometheus.CounterOpts{<br> Name: "http_requests_total",<br> Help: "Total HTTP requests",<br> },<br> []string{"method", "status"},<br>)<br>reg.MustRegister(reqCounter) // 必须显式注册<br>http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
Counter 和 Histogram 标签怎么设才不爆炸?
标签(label)是 Prometheus 多维查询的基础,但也是 cardinality 灾难的源头。比如把用户 ID、请求路径全塞进标签,短短几天就可能生成上百万时间序列,压垮 Prometheus 本地存储和查询性能。
- 绝对避免:用
user_id、request_id、完整path(如/api/v1/users/12345)做标签 - 推荐做法:只保留高基数稳定、低变化维度,如
method="GET"、status="200"、handler="/api/users"(路径需提前聚合) - Histogram 分桶别照抄文档:
prometheus.DefBuckets(.005~10秒)对内部 RPC 可能太宽,对前端接口又太细;根据真实 P99 延迟调整,例如[]float64{0.01, 0.05, 0.1, 0.3, 0.6, 1.0, 3.0}
如何让中间件自动打点而不侵入业务逻辑?
GoFrame、Gin、Echo 等框架都支持中间件,这是埋点的最佳位置——不用改 handler,就能统计请求量、延迟、错误。但要注意两点:一是延迟必须用 time.Since() 在 defer 中计算,二是 error 判断要覆盖所有可能返回路径(包括 panic 捕获)。
- 典型漏点:只在
if err != nil分支里 +1 错误计数,却忘了http.Error()或状态码非 2xx 的情况 - 安全做法:统一在中间件末尾统计 status,用
written标记是否已写响应(防止 panic 后重复统计) - 示例伪代码:
start := time.Now()<br>next.ServeHTTP(w, r)<br>latency := time.Since(start).Seconds()<br>reqHist.WithLabelValues(r.Method, strconv.Itoa(status)).Observe(latency)
本地调试时 metrics 总是不更新?
因为 Prometheus 是 pull 模型,它只在 scrape 间隔(默认 15s)拉一次。你改了代码、重启服务,但浏览器刷新 /metrics 看不到最新值?那很可能是因为指标对象被重复初始化,或者注册表没复用。
- 常见错误:每次 HTTP 请求都 new 一个
CounterVec,导致旧指标内存泄漏,新指标无法注册 - 正确姿势:指标对象全局唯一,初始化一次,注入到 handler 或中间件中复用
- 验证方法:curl -s http://localhost:8080/metrics | grep http_requests_total —— 正常应看到值随请求递增,且 label 组合固定
真正难的不是写几行 NewCounter,而是想清楚哪些维度值得监控、哪些标签会反噬系统、以及指标生命周期是否和应用实例一致。很多团队上线后才发现指标膨胀,不得不回滚注册逻辑——这时候再补限流、降维、分片,代价远高于设计阶段多花十分钟画张标签矩阵图。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











