直接暴露/metrics端点是gin集成prometheus最轻量可控方式;go-gin-prometheus因路径标签失真、不支持自定义注册器、错误处理冗余及长期未更新等问题,不推荐使用。

直接暴露 /metrics 端点是 Gin 集成 Prometheus 最轻量且最可控的方式,无需第三方中间件也能稳定工作。
为什么不用 go-gin-prometheus?
这个库封装了基础指标(如请求计数、耗时 Histogram),但存在几个实际问题:
- 默认注册的
http_requests_total和http_request_duration_seconds标签中path值是原始路由(如/api/v1/users/:id),不是实际请求路径(如/api/v1/users/123),导致 PromQL 聚合失真 - 它内部调用
promhttp.Handler()但不支持自定义Registry,无法和业务自定义指标共用同一注册器 - 错误处理逻辑较重,比如对 404 请求也计入
http_requests_total,掩盖真实失败率 - 已近 3 年未更新,与最新
client_golangv1.16+ 的promauto行为不完全兼容
手动注册 + 暴露 /metrics 端点
核心是两件事:注册指标、挂载 handler。推荐用 promauto 管理生命周期,避免重复注册 panic:
- 在
main.go或初始化模块中定义指标变量(必须包级或全局,不能函数内):var ( httpRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total HTTP requests", }, []string{"method", "route", "status"}) httpRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request duration in seconds", Buckets: prometheus.DefBuckets, }, []string{"method", "route", "status"}) ) - 写一个中间件提取真实
route(即匹配到的 Gin 路由模板):func MetricsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() <pre class="brush:php;toolbar:false;"> route := c.FullPath() // ← 关键:用 FullPath() 而非 Request.URL.Path status := strconv.Itoa(c.Writer.Status()) method := c.Request.Method httpRequestsTotal.WithLabelValues(method, route, status).Inc() httpRequestDuration.WithLabelValues(method, route, status).Observe(time.Since(start).Seconds()) }}
- 在路由初始化后挂载
/metrics:r := gin.Default() r.Use(MetricsMiddleware()) r.GET("/metrics", gin.WrapH(promhttp.Handler()))
promhttp.Handler() 的配置陷阱
默认 promhttp.Handler() 使用全局 prometheus.DefaultRegisterer,但如果你用了 promauto.With 自定义 registry,就必须显式传入:
- 若你创建了独立 registry:
reg := prometheus.NewRegistry() httpRequestsTotal = promauto.With(reg).NewCounterVec(…) // … r.GET("/metrics", gin.WrapH(promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))) - 否则会报错:
panic: duplicate metrics collector registration attempted - 生产环境建议加 gzip 支持(需额外中间件或用
promhttp.HandlerOpts{EnableOpenMetrics: true}) - 不要把
/metrics暴露在公网——它不含认证,应通过反向代理限制 IP 或加 Basic Auth
验证端点是否生效
启动服务后,直接 curl 或浏览器访问 /metrics,确认返回内容包含你定义的指标名,且格式为标准 Prometheus 文本协议:
- 首行必须是
# HELP http_requests_total Total HTTP requests - 指标行形如:
http_requests_total{method="GET",route="/api/v1/users/:id",status="200"} 12 - 若返回空或 404,检查是否漏掉
r.GET("/metrics", ...);若返回 HTML 或 JSON,说明没用gin.WrapH()包装 handler - Prometheus server 抓取失败时,查日志看是否出现
server returned HTTP status 401 Unauthorized—— 这说明你加了鉴权但没配 scrape auth
真正容易被忽略的是 FullPath() 和 Request.URL.Path 的区别:前者是 Gin 解析出的路由模板(带 :id),后者是原始 URL(含具体 ID)。监控聚合必须基于模板,否则每个用户 ID 都变成独立 label,cardinality 爆炸。这个细节不处理,后续所有 PromQL 查询都会偏移。











