生产环境必须用 echo-contrib/prometheus 手动注册:默认 echo-prometheus 直方图分桶失配、path 标签未归一化致内存爆炸、不支持自定义标签。

别用 echo-prometheus,它默认配置上线即翻车:直方图分桶全挤在 5ms、path 标签未归一化导致内存爆炸、不支持自定义标签——生产环境必须换 echo-contrib/prometheus 手动注册。
为什么 echo-prometheus 默认中间件不能上生产
它暴露的指标看着全,但三个硬伤直接卡死可观测性:
-
http_request_duration_seconds直方图用的是prometheus.DefBuckets([0.005, 0.01, 0.025, ...] 秒),而你的 API 95% 耗时在 100ms 内,结果 P99 显示为 0.005s,实际可能是 2.8s - 所有原始路径(如
/api/v1/users/123)直接当path标签值写入,user_id=123这种动态值会无限生成新指标实例,几分钟吃光内存 - 不支持加
service_name、env等关键标签,Grafana 下钻时没法按服务或环境切片
用 echo-contrib/prometheus 手动注册指标
它把注册和采集拆开,你能控制每个环节。关键不是“挂中间件”,而是“怎么注册+怎么打点”:
- 导入包:
"github.com/labstack/echo-contrib/prometheus"(注意不是echo-prometheus) - 初始化时显式传入
prometheus.NewPrometheus("myapp", nil),第二个参数可设Registry或留nil用默认注册器 - 调用
p.Use(e)挂载中间件,它只负责计数和耗时采集,不自动注册任何指标 - 自定义指标必须在启动时注册一次,例如:
prometheus.MustRegister(customCounter),绝不能在 handler 里重复注册
示例片段:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
import (
"github.com/labstack/echo-contrib/prometheus"
"github.com/prometheus/client_golang/prometheus"
)
p := prometheus.NewPrometheus("myapp", nil)
p.Use(e)
customCounter := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "myapp_custom_requests_total",
Help: "Total custom requests",
},
[]string{"method", "path"},
)
prometheus.MustRegister(customCounter)
// 在 handler 中:
customCounter.WithLabelValues(c.Request().Method, "/api/v1/users").Inc()
必须做路径截断和标签白名单
这是线上事故最高频原因。不要依赖中间件自动提取 r.URL.Path,自己处理:
- 从 Echo 的
c.Request().URL.Path提取后,用正则或字符串匹配替换动态段,例如/api/v1/users/123→/api/v1/users/{id} - 对
method、status做合法性校验:空字符串、非法字符(如空格、斜杠)必须转成"unknown" - 禁止把 query 参数、header 值、用户输入内容直接当 label 值,否则 cardinality 爆炸不可逆
-
WithLabelValues()传参前务必检查:if val == "" { val = "unknown" }
暴露 /metrics 端点不能漏
只注册指标不暴露端点,Prometheus 抓不到数据,Targets 页面永远显示 DOWN:
- 必须显式调用:
e.GET("/metrics", echo.WrapHandler(promhttp.Handler())) - 别用
http.Handle("/metrics", ...)混用标准库,Echo 路由链路会绕过中间件,状态码可能被重写 - 确保这行代码在
e.Start()之前执行,且没被 Recovery 中间件拦截(否则 panic 会导致整个/metrics返回 500) - 如果用了自定义
Registry,必须用promhttp.HandlerFor(registry, promhttp.HandlerOpts{})替代promhttp.Handler()
最易忽略的是:路径模板化必须在打点前完成,而不是靠 Grafana 查询时用 regex 补救——指标一旦写入,高基数就已发生,删不掉。

















