在Gin中注册全局监控中间件必须在路由注册前调用engine.Use(),否则不生效;需区分Go进程指标(如runtime.ReadMemStats)与容器真实资源(如/cgroup/memory),并用专用无中间件路由暴露/metrics以保障Prometheus抓取稳定。

如何在 Gin 中注册全局资源监控中间件
直接把监控逻辑塞进 gin.HandlerFunc 里,再用 engine.Use() 注册,是最简路径。但注意:必须在所有路由注册前调用,否则中间件不会生效——这是新手最常漏掉的顺序问题。
典型错误是先写 r.GET("/api", handler) 再调 r.Use(monitorMiddleware),结果监控完全不触发。Gin 的中间件链只对后续注册的路由生效。
- 监控中间件应放在
gin.Default()或gin.New()之后、任何r.GET/POST之前 - 若只监控部分路由组(如仅
/admin),改用adminGroup.Use(monitorMiddleware) - 避免在中间件里做阻塞操作(如同步 HTTP 请求、大文件读取),否则会拖垮整个请求队列
监控 CPU 和内存时该用 runtime 还是 /proc?
在 Linux 容器环境(Docker/K8s)中,runtime.ReadMemStats() 返回的是 Go 进程视角的内存,不含 OS 缓存、共享库等真实占用;而读取 /proc/self/stat 或 /sys/fs/cgroup/memory/memory.usage_in_bytes 才反映容器实际资源消耗。两者差值可能高达几百 MB,误判风险极高。
推荐组合使用:
立即学习“go语言免费学习笔记(深入)”;
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 用
runtime.ReadMemStats()监控 Go 堆增长和 GC 频率(判断内存泄漏) - 用
os.Open("/sys/fs/cgroup/memory/memory.usage_in_bytes")获取容器级内存上限与当前用量(判断是否接近 OOM) - CPU 使用率建议采样
/proc/stat的 user/sys/idle 时间戳,计算 10 秒间隔差值,比runtime.NumCPU()更准
为什么 HTTP 请求延迟统计总比真实慢 200ms?
常见原因是中间件里用了 time.Now() 记录开始时间,但没考虑 Gin 的 Context 生命周期——如果请求被 Abort() 或提前返回(如鉴权失败、参数校验拦截),Next() 后的结束时间采集就失效了,导致统计口径不一致。
正确做法是用 c.Set("start_time", time.Now()) 存入上下文,并在 defer 函数中统一读取:
func monitorMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Set("start_time", start)
defer func() {
duration := time.Since(start)
log.Printf("req: %s %s, dur: %v", c.Request.Method, c.Request.URL.Path, duration)
}()
c.Next()
}
}
- 不要依赖
c.Writer.Size()判断响应是否已写入——它只反映 header 发送量,body 可能还没 flush - 若启用了 gzip 中间件,
c.Writer.Size()返回的是压缩后字节数,需额外标记原始大小 - 高并发下频繁打日志会成为瓶颈,建议聚合采样(如每秒最多打 10 条慢请求)
如何安全暴露监控指标给 Prometheus
别把 /metrics 路由直接挂到主引擎上。默认 Gin 的 gin.DefaultWriter 不支持流式响应,Prometheus 抓取时可能因超时或缓冲区满而失败。
必须用专用 handler 并禁用 Gin 的日志和 recovery:
r := gin.New()
r.Use(gin.Recovery()) // 全局 recovery
// 单独为 metrics 创建无中间件路由
metricsRouter := gin.New()
metricsRouter.GET("/metrics", func(c *gin.Context) {
c.Header("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
// 输出 prometheus 格式文本,例如:
c.String(200, `http_request_duration_seconds_sum{path="/api/user"} 0.123`)
})
- 务必限制
/metrics的访问来源(如只允许 127.0.0.1 或内网 IP),避免暴露敏感指标 - 不要在 metrics handler 里调用
c.Next()——它没有下游,调用会 panic - 若用第三方库(如
promhttp),需手动适配 Gin 的http.ResponseWriter接口,不能直接传c.Writer

















