用 Prometheus 监控 Go 应用:埋点实战
Go 应用接入 Prometheus 的完整教程:prometheus/client_golang 暴露 /metrics、Counter/Gauge/Histogram 三种指标埋点、中间件统一采集 HTTP 指标、常见问题。
Go 应用接入 Prometheus 只需一个库:prometheus/client_golang。暴露 /metrics 端点,埋点自定义指标,Prometheus 来抓取——本文走完整个流程。
第一步:暴露默认指标
go get github.com/prometheus/client_golang/prometheus/promhttp
两行代码把默认指标(Go 运行时:goroutine 数、GC 耗时、内存)挂到 HTTP 服务上:
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
访问 http://localhost:8080/metrics 就能看到 go_goroutines、go_memstats_*、process_* 等默认指标。
第二步:埋点业务指标
Counter:累计请求数
var httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
prometheus.MustRegister(httpRequestsTotal)
// 处理请求时
httpRequestsTotal.WithLabelValues("GET", "/api/users", "200").Inc()
Gauge:当前活跃请求
var activeRequests = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "http_active_requests",
Help: "Number of active HTTP requests",
},
)
// 中间件里
activeRequests.Inc()
defer activeRequests.Dec()
Histogram:请求延迟分布
var requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Request duration in seconds",
Buckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2.5, 5},
},
[]string{"path"},
)
第三步:统一中间件
手写埋点容易漏,用中间件统一采集所有路由:
func metricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
activeRequests.Inc()
defer activeRequests.Dec()
rw := &statusWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(rw, r)
path := routePattern(r) // 用路由模板而非原始路径!
requestDuration.WithLabelValues(path).Observe(time.Since(start).Seconds())
httpRequestsTotal.WithLabelValues(r.Method, path, strconv.Itoa(rw.status)).Inc()
})
}
⚠️ 最关键的坑:标签里用路由模板(/users/{id})而不是原始路径(/users/123)。原始路径会把每个 ID 变成一条新时间序列,基数爆炸,Prometheus 会被拖垮。Gin 用 c.FullPath(),Echo 用 c.Path(),chi 用 RouteContext。
第四步:Prometheus 抓取
scrape_configs:
- job_name: go-app
scrape_interval: 15s
static_configs:
- targets: ['app:8080']
查询验证:
rate(http_requests_total[5m])
histogram_quantile(0.99, sum by (le, path) (rate(http_request_duration_seconds_bucket[5m])))
观测云对照
如果不想自建 Prometheus 这套基础设施,观测云提供更短的路径:DataKit 可抓取应用的 /metrics 端点或直接接收 remote write;对 Go 应用更推荐接 APM 探针(无侵入或低开销)——请求速率、错误率、延迟分布自动采集,无需手写埋点,还能获得分布式追踪能力;已埋点的自定义指标照常上报,与 APM 数据在同一平台联动。
常见问题(FAQ)
Q:Counter 的值重启后归零,会不会丢数据?
A:Prometheus 的 rate/increase 会自动处理重置,长期趋势不受影响。这正是 Counter 设计成"只增"的原因——不需要进程持久化状态。
Q:goroutine 数突然飙升说明什么?
A:goroutine 泄漏的典型信号——某个地方起了 goroutine 没有退出(常见:channel 阻塞、http body 没关、context 没取消)。用 pprof 的 goroutine profile 定位泄漏点。
Q:默认指标里 process_ 和 go_ 有什么区别?**
A:go_* 是 Go 运行时视角(GC、goroutine、堆),process_* 是操作系统进程视角(CPU 时间、RSS、文件描述符)。排查内存问题两个都要看。