http.listenandserve(":8080", promhttp.handler()) 语法合法,但常见错误是漏括号写成 promhttp.handler 导致传入函数而非 handler 实例,编译报类型不匹配;若依赖默认 mux 却未注册 /metrics 路由,则返回 404。

promhttp.Handler() 不能直接传给 http.ListenAndServe() 作为第二个参数,否则编译失败或 panic;业务指标必须显式注册到默认注册器,否则 /metrics 返回空;标签值若含高基数字段(如用户 ID、URL 路径),会快速拖垮 Prometheus。
为什么 http.ListenAndServe(":8080", promhttp.Handler()) 编译报错?
因为 http.ListenAndServe() 第二个参数类型是 http.Handler 或 nil,而 promhttp.Handler() 确实返回 http.Handler —— 所以这行代码本身**语法合法**。但常见错误是误写成 http.ListenAndServe(":8080", promhttp.Handler)(漏了括号),导致传入函数值而非调用结果,编译器报 cannot use promhttp.Handler (type func() http.Handler) as type http.Handler。
更隐蔽的问题是:如果之前已往 http.DefaultServeMux 注册过其他路由(比如 http.HandleFunc("/", ...)),再调 http.ListenAndServe(":8080", nil) 会用默认 mux,但你没挂 /metrics,结果就是 404。
- 稳妥做法:新建独立 mux,避免污染全局状态
mux := http.NewServeMux()mux.Handle("/metrics", promhttp.Handler())http.ListenAndServe(":8080", mux)- 若用 Gin/Echo,必须用框架提供的包装方式,如
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
为什么 curl http://localhost:8080/metrics 看不到自定义指标?
根本原因不是路由没挂对,而是指标对象压根没进 Prometheus 的注册表。运行时指标(go_*、process_*)是自动注册的,所以你能看到它们;但你写的 prometheus.NewCounterVec() 只是个 Go 结构体,不调 prometheus.MustRegister() 就不会出现在 /metrics 输出里。
- 注册必须在
main()开头或init()中执行,且只做一次 - 别把指标定义写在 handler 函数里,否则每次请求都新建+注册 → panic:
duplicate metrics collector registration - 检查
Name:字段是否和 curl 输出里完全一致(大小写、下划线、拼写) - 如果用了自定义
prometheus.NewRegistry(),就得配promhttp.HandlerFor(reg, nil),不能用默认promhttp.Handler()
如何避免 CounterVec 标签引发高基数崩溃?
把 r.URL.Path 或 r.Header.Get("X-Request-ID") 直接塞进 WithLabelValues(),等于给 Prometheus 喂毒——每个请求生成一条新时间序列,几万用户一并发,内存就爆了。
- 路径动态段必须预处理:
/api/v1/users/123→/api/v1/users/{id} - 状态码别直接用
r.StatusCode,映射为有限字符串:switch r.StatusCode { case 200: "200"; case 404: "404"; default: "other" } - 标签名必须是合法标识符:
tenant-id是非法的,得写成tenant_id - 高基数字段(用户 ID、订单号)一律禁止进 label;如需关联,走日志 + tracing ID 对齐,别硬塞指标
选错指标类型会让监控完全失真
Counter 和 Gauge 语义完全不同,互换使用会导致查询结果毫无意义。Prometheus 不支持改指标类型,历史数据作废。
- 累计值(总请求数、总错误数)→ 用
Counter,只调.Inc()或.Add() - 瞬时值(当前活跃连接数、内存占用 MB)→ 用
Gauge,goroutine 退出时必须.Dec(),否则数值永远不准 - 响应延迟分布 → 必须用
Histogram,别用Gauge记 P99;Buckets要按真实 P99 设(比如 300ms 就用[]float64{0.05, 0.1, 0.2, 0.3, 0.5, 1.0}),别套prometheus.DefBuckets - 别用
Counter统计活跃连接——它不能减,泄漏后指标只涨不跌
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











