必须自己定义并注册带业务标签的指标,且手动挂载/metrics路由;echo-prometheus默认仅支持method/status/path三标签,不支持tenant_id等业务维度,且硬编码label列表、不提供扩展接口,导致无法注入自定义标签。

直接用 echo-prometheus 中间件只能暴露基础 HTTP 指标(如请求数、状态码、延迟),想按业务维度(比如 tenant_id、workflow_stage、api_version)打点,必须自己定义并注册带标签的指标,且不能漏掉 /metrics 路由挂载——这是 90% 的“监控没数据”问题根源。
为什么 echo-prometheus 默认不支持业务标签?
echo-prometheus 的 NewMiddleware() 内部只用了无标签的 prometheus.NewCounterVec 和 prometheus.NewHistogramVec,但它的 label 名称列表是硬编码为 []string{"method", "status", "path"},且不提供外部传入自定义 label 的接口。这意味着:
- 你无法在指标里加入
tenant_id或user_type这类真实业务维度 - 路径中带 ID 的请求(如
/orders/123)会被当作独立 label 值,导致指标基数爆炸 - 它注册的指标走的是默认注册器(
prometheus.DefaultRegisterer),和你自己用prometheus.NewRegistry()创建的注册器互不兼容
如何安全添加带业务标签的自定义指标?
核心原则:指标变量必须是包级变量,注册必须在 main() 开头或 init() 中完成,打点必须在 handler 内异步进行(避免阻塞)。示例:
var (
// 定义 CounterVec:按 service + tenant_id + status 统计请求
reqCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests processed",
},
[]string{"service", "tenant_id", "status"},
)
// 定义 Histogram:记录处理延迟,带 workflow_stage 标签
latencyHist = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5},
},
[]string{"service", "workflow_stage"},
)
)
<p>func init() {
// 必须显式注册,否则 /metrics 不会返回这些指标
prometheus.MustRegister(reqCounter)
prometheus.MustRegister(latencyHist)
}</p><p>func main() {
e := echo.New()</p><pre class="brush:php;toolbar:false;">// 手动挂载 /metrics,不能依赖中间件自动注册
e.GET("/metrics", echo.WrapHandler(promhttp.Handler()))
e.GET("/orders/:id", func(c echo.Context) error {
start := time.Now()
tenantID := c.Request().Header.Get("X-Tenant-ID")
if tenantID == "" {
tenantID = "unknown"
}
// 打点:注意 WithLabelValues 顺序必须和定义时 []string 严格一致
reqCounter.WithLabelValues("order-service", tenantID, "200").Inc()
// 模拟业务逻辑
time.Sleep(10 * time.Millisecond)
// 延迟打点
latencyHist.WithLabelValues("order-service", "validation").Observe(time.Since(start).Seconds())
return c.String(http.StatusOK, "ok")
})
e.Logger.Fatal(e.Start(":8080"))}
关键点:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 标签名必须全小写+下划线(
tenant_id),不能用tenant-id或TenantID - 路径参数
:id不能直接进 label,应统一替换为占位符(如"/orders/{id}")再打点,否则基数爆炸 - 高基数字段(如用户手机号、traceID)绝不能塞进 label,应改用采样上报或聚合后统计错误类型
如何避免 /metrics 返回空或 404?
常见错误是只调了 MustRegister(),却忘了挂路由。Echo 不像 Gin 或标准库 http 那样自动绑定 /metrics。必须手动加:
- 用
e.GET("/metrics", echo.WrapHandler(promhttp.Handler()))是最简方式 - 若用了自定义注册器(如
reg := prometheus.NewRegistry()),则必须用promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),不能用默认promhttp.Handler() - 别在 handler 里动态创建新指标再注册——Go 的 Prometheus client 不支持运行时重复注册同名指标,会 panic
多维度告警时 label 设计最容易被忽略什么?
不是“要不要加 label”,而是“哪些 label 绝对不能一起组合”。例如:
-
{tenant_id, user_id}:用户 ID 基数上亿,组合后指标数爆炸,Prometheus 存储和查询直接卡死 -
{path, method, status}看似合理,但未归一化路径(如/users/123vs/users/456)会导致每条请求生成新时间序列 - 正确做法是预定义有限 label 集合:
tenant_id(几十个)、service(几个)、workflow_stage("auth"/"validate"/"persist")——控制总维度在百量级以内
真正难的不是写代码,是设计 label 的边界:既要足够区分业务场景,又不能让 Prometheus 自己先崩掉。










