必须显式注册 promhttp.handler() 到路由,否则 curl /metrics 返回 404;gin 中需用 gin.wraph(promhttp.handler()) 绑定,且路径须与 prometheus 配置中 metrics_path 严格一致。

必须显式注册 promhttp.Handler() 到路由,否则 curl /metrics 一定返回 404 或空响应——这是 90% 的初学者卡住的地方。
为什么 Gin 启动后 /metrics 返回 404
因为 Gin 默认不挂载任何指标 handler,prometheus.MustRegister() 只是把指标注册进默认 registry,但不会自动暴露 HTTP 端点。
-
promhttp.Handler()必须手动绑定到某个路由,路径名(如/metrics)要和 Prometheus 配置里的scrape_configs.job.metrics_path完全一致(默认就是/metrics) - Gin 中不能直接写
r.GET("/metrics", promhttp.Handler())——类型不匹配,promhttp.Handler()返回的是http.Handler,而 Gin 的GET方法需要gin.HandlerFunc - 正确写法是
r.GET("/metrics", gin.WrapH(promhttp.Handler()));如果用标准库http.ServeMux,则是http.Handle("/metrics", promhttp.Handler()) - 别在多个地方重复调用
gin.WrapH(promhttp.Handler()),比如在中间件里又 wrap 一次,会导致 panic 或响应异常
自定义指标必须定义为包级变量且只注册一次
把 prometheus.NewCounterVec() 写在 handler 函数里、或在 init() 中多次执行 MustRegister(),都会触发 panic:“duplicate metrics collector registration attempted”。
- 所有业务指标(如请求计数、延迟直方图)必须声明为包级变量,例如:
var httpReqCounter = prometheus.NewCounterVec(...) - 注册动作统一放在
init()或main()开头,仅一次:prometheus.MustRegister(httpReqCounter) - 若用
promauto.NewCounterVec(),它内部已自动注册,无需再调MustRegister(),但要注意它默认注册到全局 registry,多模块共用时可能冲突 - 标签(
[]string{"method", "status", "path"})中避免填动态高基数字段,比如user_id或完整 URL 路径;应提前归一化,如将/api/user/123改为/api/user/{id}
HTTP 中间件里记录指标时,ResponseWriter 必须包装
不包装 http.ResponseWriter,就捕获不到真实状态码和写入时间,导致 status="200" 占比虚高、延迟统计失真。
- 要用
responsewriter.WrapWriter或自己实现一个 wrapper,重写WriteHeader()和Write()方法来记录状态码和耗时 - 别在中间件里直接用
w.WriteHeader(statusCode)就以为记下了——原生ResponseWriter不暴露当前状态,必须靠 wrapper 拦截 - 直方图桶(
Buckets)别用默认的prometheus.DefBuckets;对 API 延迟,推荐显式设为[]float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5}(单位:秒) - 活跃连接数、队列长度这类瞬时值,必须用
Gauge,不是Counter;用Counter记在线人数,goroutine 退出时无法减,指标只会单向暴涨
运行时指标不用注册,但路径和监听地址常被忽略
NewGoCollector() 已内置并自动注册 Go 运行时指标(如 go_goroutines、go_memstats_alloc_bytes),你不需要也不应该再手动注册它们——但很多人忘了检查 Prometheus 抓取目标的实际可访问性。
- 本地启动
:8080时,默认监听localhost:8080,Docker 容器或远程 Prometheus server 无法访问;应改用0.0.0.0:8080 - 确保防火墙放行端口,Kubernetes 中 Service 类型要是
ClusterIP或带 NodePort,且targetPort对准你的应用端口 - 路径大小写敏感:
/Metrics和/metrics是两个不同 endpoint;Prometheus 配置中的metrics_path必须和 Gin 中注册的路径字面量完全一致 - 如果用了自定义 registry(比如
reg := prometheus.NewRegistry()),暴露 handler 时得用promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),不能用裸的promhttp.Handler()
最易被忽略的其实是 label 维度控制和监听地址绑定——前者导致 Prometheus server OOM,后者让整个监控链路静默失效,连日志都看不到错误。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











