直接用 promhttp.handler() 会漏掉 http 状态码和响应时间,因为它只暴露 /metrics 端点,不拦截业务请求;需用中间件(如 instrumenthandler 或自定义 handler)在 servehttp 前后记录指标。

为什么直接用 promhttp.Handler() 会漏掉 HTTP 状态码和响应时间?
因为 promhttp.Handler() 只暴露 Prometheus 自身的指标端点(如 /metrics),它不拦截你的业务 HTTP 请求,自然无法统计你服务的 http_request_duration_seconds 或 http_request_total。真正要埋点,得在请求处理链路里插中间件。
- 必须自己写一个 HTTP 中间件,包装
http.Handler,在ServeHTTP前后记录开始/结束时间、状态码、路径等 - 推荐用官方库
github.com/prometheus/client_golang/prometheus/promhttp提供的InstrumentHandler系列函数,但注意它们已标记为 deprecated —— 新项目应优先用promhttp.NewHandler()配合自定义Handler实现 - 别把指标注册写在 handler 内部,否则每次请求都重复注册,会 panic 报错
"duplicate metrics collector registration attempted"
如何用 prometheus.NewCounterVec 区分不同 API 路径和方法?
硬编码路径或方法名会导致指标爆炸(比如带 ID 的路由 /user/123 会生成无数 label),必须做路径模板化。Gin 或 Echo 框架里可通过上下文提取路由模式,而不是原始 URL。
- Gin 示例:在中间件中用
c.FullPath()获取注册时的路由模式(如/api/v1/users/:id),传给counter.WithLabelValues(c.Request.Method, c.FullPath()) - label 名称必须合法:只能是字母、数字、下划线,且不能以数字开头;避免用用户输入内容(如 query 参数)作 label,容易导致 cardinality 爆炸
-
NewCounterVec和NewHistogramVec初始化时要预设Buckets,比如响应时间常用prometheus.ExponentialBuckets(0.001, 2, 12)(1ms 到 ~2s)
为什么 http_request_duration_seconds 的 histogram 分位数总是 0?
常见原因是没调用 Observe(),或者调用时机不对 —— 比如在 defer 里用 time.Since(start) 但 start 时间戳被闭包捕获错误,或中间件顺序导致 handler panic 后没执行 Observe。
- 确保每个请求都走到
observe调用,建议用defer+ 显式计时,例如:start := time.Now() defer func() { duration := time.Since(start).Seconds() requestDuration.WithLabelValues(method, path, status).Observe(duration) }() - status 必须是字符串格式的 HTTP 状态码(如
"200"),不是 int;如果 handler panic,需 recover 并手动设为"500" - 检查是否启用了
promhttp.HandlerOpts.EnableOpenMetrics—— 某些旧版 Prometheus server 不支持 OpenMetrics 格式,会导致 histogram 数据解析失败
自定义业务指标(比如订单创建数)该注册在哪儿?
全局变量 + init() 注册是最简单也最稳妥的方式,只要保证只注册一次,且在 HTTP server 启动前完成。
- 定义:
var orderCreatedTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "order_created_total", Help: "Total number of orders created", }, []string{"source", "payment_method"}, ) - 注册:
func init() { prometheus.MustRegister(orderCreatedTotal) } - 使用:在业务逻辑里直接
orderCreatedTotal.WithLabelValues("app", "alipay").Inc(),无需传参或依赖上下文 - 切忌在 goroutine 里反复调用
MustRegister,也不要在 handler 里 new 指标对象 —— 指标对象本身是线程安全的,但重复注册会 panic
最难的不是埋点,是 label 设计和 cardinality 控制。一个没想清楚的 label(比如用户邮箱),可能让单个指标分裂出几百万时间序列,直接拖垮 Prometheus。先跑通再优化,但 label 一旦上线就很难改。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











