必须显式注册http.Handle("/metrics", promhttp.Handler())并确保指标与handler使用同一registry实例,否则返回404或解析失败;禁用JSON中间件、gzip、鉴权等,标签需清洗且顺序固定,高频打点应缓存WithLabelValues结果。

Go 程序要被 Prometheus 抓取,必须暴露符合规范的 /metrics 端点,且指标注册与 handler 必须使用同一 registry 实例;否则会返回 404 或解析失败 —— 这不是网络或防火墙问题,而是内部注册不一致导致的。
怎么让 Go 程序正确暴露 /metrics 端点
别自己拼字符串或加 JSON 中间件,promhttp.Handler() 已内置 text/plain 格式、UTF-8 编码和基础压缩逻辑。
- 导入
"github.com/prometheus/client_golang/prometheus/promhttp" - 在
http.ListenAndServe前注册:http.Handle("/metrics", promhttp.Handler()) - 确保该路由注册早于其他中间件(如日志、鉴权),否则可能被
http.NotFoundHandler拦截 - 不要在
/metrics上套用 gzip、JWT 验证或 CORS 中间件 —— 它只服务 Prometheus 抓取,非浏览器请求
Counter 和 CounterVec 选错会毁掉监控可读性
全局计数器(如“程序启动次数”)用 prometheus.NewCounter;但 HTTP 请求、数据库调用、队列消费等绝大多数场景,必须用 CounterVec,否则所有维度混在一起,查不出哪条路径、哪个状态码出了问题。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 定义时标签顺序必须固定:
[]string{"method", "path", "status"},打点时.WithLabelValues("GET", "/api/user", "200")顺序错一位就 panic - 路径含变量(如
/user/123)要预处理成/user/{id},否则每条 ID 都生成新指标,导致 cardinality 爆炸 - 标签值禁止空格、换行、双引号;建议用
strings.ReplaceAll(path, " ", "_")清洗 - 高频打点场景下,别在循环里反复调用
WithLabelValues—— 提前缓存prometheus.Counter实例,例如:counterGet200 := requestsTotal.WithLabelValues("GET", "/health", "200")
为什么 promhttp.Handler() 返回 404 或 500
根本原因不是端口不通,而是指标注册器和 handler 使用了不同 registry 实例 —— 默认 registry 是全局单例,但一旦你显式创建了新 registry(比如为了隔离测试指标),就必须让 handler 明确指向它。
立即学习“go语言免费学习笔记(深入)”;
- 若用默认 registry:直接
prometheus.MustRegister(...)+promhttp.Handler()即可 - 若用自定义 registry:
reg := prometheus.NewRegistry()→reg.MustRegister(...)→promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) - 检查是否误用了
promauto.NewCounter(自动注册到默认 registry)却配了自定义 handler,导致指标“注册了但 handler 看不见” - 启动时打印
prometheus.DefaultGatherer.Gather()结果,能快速验证指标是否真被注册进去
最常被忽略的是标签值清洗和 registry 实例一致性 —— 前者导致指标爆炸拖垮 Prometheus,后者让整个监控链路静默失效,连错误日志都不报。

















