第一步:创建公共模块 (monitor/monitor.go)
在你的项目中创建一个 monitor 目录,并新建 monitor.go 文件:
package monitor
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
// 1. 定义请求总数指标
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
// 2. 定义请求耗时指标
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request latency distribution",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
)
// Init 初始化监控模块,传入 Gin 引擎即可一键接入
func Init(r *gin.Engine) {
// 注册 Go 运行时指标(Goroutine、GC、内存等)
prometheus.MustRegister(prometheus.NewGoCollector())
// 暴露 /metrics 接口(供 Prometheus 拉取数据)
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
// 暴露 /healthz 健康检查接口(供 K8s 或负载均衡器探活)
r.GET("/healthz", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "UP"})
})
// 注册全局监控中间件
r.Use(monitorMiddleware())
}
// 内部监控中间件
func monitorMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// 执行后续的业务 Handler
c.Next()
// 请求结束后,记录指标
duration := time.Since(start).Seconds()
status := c.Writer.Status()
// 使用路由模板(如 /user/:id),防止标签基数爆炸
path := c.FullPath()
if path == "" {
path = "unknown" // 处理未匹配到路由的 404 请求
}
httpRequestsTotal.WithLabelValues(c.Request.Method, path, string(rune(status))).Inc()
httpRequestDuration.WithLabelValues(c.Request.Method, path).Observe(duration)
}
}
第二步:在你的微服务中使用 (main.go)
封装好之后,你的微服务主程序将变得极其简洁:
package main
import (
"your_project/monitor" // 引入你刚才创建的公共模块
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 🎉 核心:只需这一行代码,即可自动注入 /metrics、/healthz 以及全局监控中间件
monitor.Init(r)
// 下面只需要写你的纯业务代码
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
r.Run(":8080")
}
避坑说明
防标签基数爆炸:在中间件中,我使用了 c.FullPath() 而不是 c.Request.URL.Path。如果用户请求 /user/123,FullPath 会返回路由模板 /user/:id。这能避免 Prometheus 中产生成千上万个不同的标签导致内存溢出。
处理 404 请求:当用户访问不存在的路由时,c.FullPath() 会返回空字符串。代码中加了 if path == "" { path = "unknown" } 的防御性编程,防止 Prometheus 报错。
内置 Go 运行时监控:在 Init 函数中自动注册了 prometheus.NewGoCollector()。这意味着你不仅能看到 QPS 和延迟,还能在 Grafana 里直接看到当前服务的 Goroutine 数量、内存分配 和 GC 耗时,排查内存泄漏和协程泄漏极其方便。
接口隔离:/metrics 和 /healthz 注册在 r.Use() 之前,天然免疫监控中间件,不会把基础设施的探活请求也统计进业务 QPS 中。