推荐直接使用 prometheus/client_golang 暴露指标,它提供线程安全的 Counter、Gauge、Histogram 和 Summary,初始化需注册到默认注册表,HTTP handler 复用 promhttp.Handler(),注意 Counter 与 Gauge 语义区分、避免同名指标重复注册、中间件埋点确保 defer 中上报并控制 label 基数,排查优先检查注册、路由和端口。

Go 里用 prometheus/client_golang 暴露指标最直接
不推荐自己造轮子统计计数器或直方图,prometheus/client_golang 是事实标准,且与 Prometheus 生态天然兼容。它提供线程安全的 Counter、Gauge、Histogram 和 Summary,底层自动处理并发写入和采样逻辑。
实操建议:
- 初始化时注册指标到默认注册表:
prometheus.MustRegister(...),避免漏注册导致指标不暴露 - HTTP handler 直接复用
promhttp.Handler(),不要自己拼响应体,否则可能违反 OpenMetrics 格式 - 若服务已用
http.ServeMux,用http.Handle("/metrics", promhttp.Handler())即可,无需额外路由逻辑 - 本地调试时 curl
curl http://localhost:8080/metrics看输出是否含# TYPE行和指标值,不是空页或 404
定义 Counter 和 Gauge 时别混淆语义
Counter 只增不减,适合累计量(如请求总数、错误总次数);Gauge 可增可减,适合瞬时值(如当前 goroutine 数、内存使用字节数)。混用会导致 Prometheus 查询结果异常,比如用 Gauge 记请求总数,重启后数值跳变会触发误告警。
常见错误现象:
立即学习“go语言免费学习笔记(深入)”;
- 用
Gauge.Set()更新请求数,但服务重启后指标归零,Prometheus 的rate()计算崩掉 - 对
Counter调用Inc()前没检查是否已初始化,panic 报panic: counter cannot decrease - 多个包各自定义同名
Counter(如都叫http_requests_total),注册时报错duplicate metrics collector registration attempted
正确做法:统一在 init() 或 main() 开头定义并注册,命名加前缀(如 myapp_http_requests_total),用 prometheus.NewCounterVec 支持 label 维度。
HTTP 中间件里埋点要小心生命周期和 label 粒度
把指标收集塞进 HTTP handler 链是最常见场景,但容易忽略两点:一是 handler 执行完毕前指标就上报,导致耗时统计不准;二是 label 值带敏感信息(如用户 ID)或高基数字段(如 request ID),造成 Prometheus 内存暴涨。
实操建议:
- 用
http.HandlerFunc包裹原始 handler,在defer里调用Observe()或Inc(),确保耗时统计包含整个 handler 执行时间 - label 值只取低基数、业务稳定字段,例如
method="GET"、status_code="200"、path="/api/users";绝对避免user_id="123456789" - 如果必须按用户维度聚合,改用后端日志 + Loki + Grafana,别压给 Prometheus
- 测试中间件时,用
httptest.NewRecorder()检查是否真调用了指标方法,而不是只看 HTTP 状态码
本地跑不通 prometheus?先确认端口和路径没被占或写错
新手常卡在“metrics 页面打不开”,90% 是因为:http.ListenAndServe(":8080", nil) 启动后没挂 metrics handler,或者 handler 注册了但 mux 没生效,又或者防火墙/SELinux 拦了端口。
快速排查步骤:
- 启动服务后,执行
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows),确认 Go 进程确实在监听 - curl
curl -v http://localhost:8080/metrics,看响应头是否有Content-Type: text/plain; version=0.0.4,没有说明 handler 没挂对 - 检查代码里是否写了
http.Handle("/metrics", ...),而不是http.HandleFunc("/metrics", ...)—— 后者传的是函数,前者才传 handler 实例 - 如果用 Gin/Echo 等框架,别直接套
promhttp.Handler(),得用对应框架的适配器(如 Gin 的gin.WrapH(promhttp.Handler()))
指标本身没那么复杂,但暴露通路稍有偏差就全白搭。盯住注册、路由、端口这三处,比调指标类型和 label 更优先。










