必须用gin.wraph(promhttp.handler()),因gin的router.get()只接受func(*gin.context)类型函数,而promhttp.handler()返回标准http.handler接口,不包装会导致编译失败或运行时panic。

直接暴露 /metrics 端点 + 手写中间件打点,是 Gin 集成 Prometheus 最稳、最可控的方式;用第三方库(如 go-gin-prometheus)反而容易因标签失真、注册冲突或错误处理冗余引入线上问题。
为什么 gin.WrapH(promhttp.Handler()) 是必须的
promhttp.Handler() 返回的是标准 http.Handler 接口,而 Gin 的 router.GET() 只接受 func(*gin.Context) 类型函数。不包装就调用会编译失败或运行时 panic。
- 正确写法:
router.GET("/metrics", gin.WrapH(promhttp.Handler())) - 漏掉
gin.WrapH会导致 404 或 panic,且错误信息不直观(常见报错:cannot use promhttp.Handler() as func(...) - 如果用了自定义
prometheus.Registry,得换用promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),再套gin.WrapH
CounterVec 必须声明为包级变量且只注册一次
所有指标(如 httpRequestsTotal)若定义在 handler 函数里,每次请求都 NewCounterVec() + MustRegister(),进程立刻 panic:duplicate metrics collector registration,且无法 recover。
- 指标变量必须是包级(全局)变量,例如:
var httpRequestsTotal = promauto.NewCounterVec(...) - 若不用
promauto,则需在init()或main()开头显式调用prometheus.MustRegister(httpRequestsTotal) - 用了
promauto.NewXXX()就别再MustRegister()——它内部已自动注册,双注册必 panic - 标签数组顺序(如
[]string{"method", "route", "status"})必须和后续WithLabelValues("GET", "/api/user", "200")参数严格一致,错一位就 panic
中间件里打点要避开三个典型坑
打点逻辑写在中间件里没问题,但细节错一点,QPS 就是 0、耗时就不准、标签就爆炸。
- 路径标签必须用
c.FullPath(),不是c.Request.URL.Path;后者带真实 ID(如/user/123),会导致 label 组合爆炸;c.FullPath()返回路由模板(如/user/:id) - 状态码必须转字符串:
strconv.Itoa(c.Writer.Status());传整数200会 panic,用r.Header.Get("Status")不可靠(可能为空或格式错) - 直方图
Observe()必须在响应真正写出后执行:放在c.Next()后;若中间件提前Observe(),测的是中间件耗时,不是端到端延迟 - 没捕获 panic 会导致
Observe()跳过,直方图缺数据;建议在 defer 里加recover()分支,统一设 status 为"500"
QPS 峰值查不到?PromQL 写法和采集逻辑都要对
直接看 http_requests_total 的原始值毫无意义;QPS 是速率,必须靠 rate() 计算,且窗口大小、抓取间隔、打点完整性三者缺一不可。
- QPS 必须用
CounterVec,不能用Gauge;Gauge记“当前 QPS”是伪需求,Prometheus 里没有瞬时 QPS 概念 - PromQL 查峰值推荐:
max_over_time(rate(http_requests_total{job="my-app"}[30s])[1h:])—— 表示过去 1 小时内,每 30 秒窗口的 QPS 最大值 - 抓取间隔(scrape_interval)必须 ≤ 窗口一半,比如用
[30s]就要求scrape_interval ≤ 15s,否则 rate 计算失真 - 确保中间件里每个请求都走到
.Inc();如果写了c.Abort()或 panic 在c.Next()前,该请求就漏统计了
最易被忽略的一点:所有指标变量即使声明为包级,若没被任何代码引用(比如中间件里忘了调 WithLabelValues().Inc()),Go 编译器可能在初始化后将其当作无用对象 GC 掉,/metrics 就返回空——连 warning 都没有。











