默认的 promhttp.Handler() 仅暴露全局请求总数、延迟直方图和错误计数,缺乏 method、path、status_code 等关键标签,无法按 HTTP 方法(如 GET/POST)和路由路径(如 /api/v1/users)多维区分定位问题。

为什么默认的 promhttp.Handler() 不够用
它只暴露整体请求总数、延迟直方图和错误计数,没法区分 GET、POST、DELETE 等方法,也没法按路由路径(比如 /api/v1/users vs /api/v1/orders)打标。一旦某个 POST 接口开始超时,你只能看到“HTTP 请求延迟升高”,但不知道是哪个方法+哪条路由拖慢的。
用 prometheus.NewCounterVec() 按 method + path 打点
核心是定义一个带标签的计数器,标签必须覆盖你后续想切分的维度:至少包括 method 和 path,建议再加 status_code:
var httpRequestTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP Requests",
},
[]string{"method", "path", "status_code"},
)
注册到 Prometheus 收集器前记得调用 prometheus.MustRegister(httpRequestTotal);否则指标不会被暴露。
在 Gin 中间件里记录时注意:
-
ctx.Request.URL.Path会包含查询参数,导致 label 爆炸(如/search?q=foo和/search?q=bar被视为两条不同路径),应改用ctx.FullPath()—— 它返回注册时的路由模板,比如/api/v1/users/:id -
status_code必须在写响应后才能拿到,所以得用ctx.Writer.Status(),且要确保中间件放在gin.Recovery()之后,否则 panic 会导致 status 始终为 0
延迟监控别直接用 SummaryVec,优先选 HistogramVec
SummaryVec 在服务重启后分位数会重置,且无法跨实例聚合;而 HistogramVec 支持服务端聚合计算 rate() 和 histogram_quantile(),更适合生产环境。
定义示例:
var httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5},
},
[]string{"method", "path", "status_code"},
)
记录时用 time.Since(start) 得到秒级浮点数,直接传入 .Observe() 即可。注意单位必须是秒(不是毫秒),否则 Grafana 里的 P95 会偏差 1000 倍。
Gin 路由组与 Prometheus label 冲突怎么处理
如果你用 router.Group("/api/v1") 注册子路由,但又希望所有 /api/v1/xxx 统一归为 api_v1 这个逻辑分组,而不是暴露完整路径(避免 label cardinality 过高),就得手动截断或映射:
- 不要依赖
ctx.FullPath()的原始值,先做字符串匹配或正则提取,例如:strings.HasPrefix(fullPath, "/api/v1/")→ label 值设为"api_v1" - 若使用 Gin 的
router.Any()或通配路由(如/:version/users),ctx.FullPath()仍能正确返回/:version/users,无需额外处理 - 对健康检查接口(如
/healthz)建议单独排除,避免污染主指标:在中间件开头加if ctx.Request.URL.Path == "/healthz" { ctx.Next(); return }
label 维度越多,Prometheus 存储压力越大;method + route_group + status_code 通常是性价比最高的组合,比全路径更稳定也更易查。


















