Go服务暴露/metrics端点不等于业务指标可被Prometheus抓取,根本原因是自定义指标必须显式调用prometheus.MustRegister()注册到DefaultRegisterer,未注册则仅返回go_、process_等运行时指标。

Go 服务暴露了 /metrics 端点,不等于 Prometheus 能抓到你想要的业务指标——90% 的监控失效问题出在注册时机、注册器污染或 handler 绑定方式上,而不是配置文件写错。
为什么 curl 能拿到数据,但 Prometheus 查不到 http_requests_total
根本原因是:运行时指标(go_*、process_*)自动注册进 prometheus.DefaultRegisterer,而你定义的 prometheus.NewCounter() 如果没显式调用 prometheus.MustRegister(),它就只是个 Go 变量,不会出现在响应里。
-
promhttp.Handler()默认只读取DefaultRegisterer,不扫描全局变量 - 如果用了
promauto.NewCounterVec()却没指定注册器,它会 fallback 到默认注册器——但前提是没被其他包提前panic掉 - 检查响应头是否含
Content-Type: text/plain; version=0.0.4,缺这个 Prometheus 直接拒收
如何避免 duplicate metrics collector registration panic
同名指标重复注册是启动即崩的高频错误,本质是多个 init() 或多个包都执行了 MustRegister()。
- 常见场景:中间件包、工具库、主应用各自定义并注册了
http_requests_total -
promauto更隐蔽:只要包被import,promauto.NewCounterVec(...)就会立即注册,无法延迟 - 安全做法:所有指标统一在
main()初始化入口处创建 + 注册;其他模块只接收指标实例(如*prometheus.CounterVec)作为参数注入 - 调试时可临时加
prometheus.Unregister(oldMetric)再注册,但生产环境应杜绝
多模块/子服务场景下必须用独立 Registry
中大型项目一旦拆分成插件、中间件、子服务,promauto 绑死 DefaultRegisterer 的行为就成了隐患。
立即学习“go语言免费学习笔记(深入)”;
- 每个子模块应创建自己的注册器:
reg := prometheus.NewRegistry() - 构造指标时传入该注册器:
prometheus.NewCounterVec(opts, labels).With(reg)(注意不是MustRegister) - 挂载 handler 时显式指定:
promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) - 好处:不同模块可用相同指标名(如都叫
api_latency_seconds),只要 registry 隔离就不会冲突
Docker 容器里 Prometheus 抓不到 Go 服务的 /metrics
典型错误是 targets 写成 ['localhost:8080']——这在 Prometheus 容器内访问的是它自己,不是你的 Go 服务。
- Mac / Windows:改用
host.docker.internal:8080 - Linux:用宿主机真实 IP(如
192.168.1.100:8080),或启动容器时加--network host - Go 服务必须监听
0.0.0.0:8080,不能只绑127.0.0.1:8080 - 确认 Go 服务端口已暴露给宿主机(Docker run 加
-p 8080:8080)
最易被忽略的一点:标签设计。一个请求 ID、用户 ID 或未过滤的 URL 路径作 label,可能让 series 数在几小时内暴涨十倍,直接拖垮 Prometheus 存储和查询性能。指标名和 label 不是越“全”越好,而是越“稳”越好。


















