正确统计接口真实响应耗时需在c.next()前存time.now()到上下文,之后读取计算差值;c.writer.status()才是状态码getter;必须重写writeheader()和write()捕获所有响应;避免日志锁和阻塞操作。

为什么 c.Next() 前后计时不准
直接用 time.Now() 在中间件入口和 c.Next() 后取差值,统计的是「中间件执行耗时」,不是「接口真实响应耗时」。比如你在中间件里做了鉴权、日志写入、参数校验,这些都会被算进去,而真正 handler 处理业务的时间反而被稀释了。
真实响应耗时应从路由匹配完成、handler 开始执行前那一刻起算——Gin 提供了标准方式:在 c.Next() 前调用 time.Now() 并存入上下文,在 handler 执行完、中间件恢复执行后读取它。
- ✅ 正确做法:
c.Set("start_time", time.Now())→c.Next()→start, _ := c.Get("start_time")→cost := time.Since(start.(time.Time)) - ❌ 错误做法:用闭包变量存
start,并发请求下会互相覆盖(Go 的 goroutine 共享闭包变量) - ⚠️ 注意:
time.Since()自动使用单调时钟(monotonic clock),比手动减time.Now().Sub()更可靠,避免系统时间回拨导致负耗时
c.Writer.Status() 为什么总返回 0 或 200
Gin 的 c.Status() 是 setter,不是 getter;真正写入响应状态码的是底层 ResponseWriter.WriteHeader()。中间件在 c.Next() 返回后读 c.Writer.Status(),但此时 handler 可能还没真正触发 WriteHeader(比如 panic、提前 abort、或没显式调用 c.AbortWithStatus())。
- ✅ 必须用
c.Writer.Status()(v1.9+ 已修复为实时 getter),不是c.Status() - ✅ 如果 handler panic,
recovery()中间件会在 logger 之后才设 status=500,所以要把自定义统计中间件放在recovery()之后注册 - ✅ 遇到
c.Abort()(如鉴权失败),status 不会自动更新,需手动补设:c.AbortWithStatus(http.StatusUnauthorized)或在中间件里判断c.IsAborted()后主动记录对应状态码 - ⚠️ 别依赖
c.Writer.Size()获取 body 长度——未写 body 时返回 -1,且 304/204 等无 body 响应也得单独处理
如何避免统计漏掉 304、204 这类无 body 响应
很多中间件只重写 Write() 方法捕获 body 大小,但 WriteHeader() 才是设置状态码的唯一入口。304、204、301 等响应不调用 Write(),只调 WriteHeader(),如果中间件没重写它,状态码和耗时就全丢了。
- ✅ 必须实现自定义
responseWriter类型,同时重写WriteHeader(int)和Write([]byte) - ✅ 在
WriteHeader()里立即记录状态码和当前耗时(此时 handler 已结束,c.Next()已返回) - ✅ 不要复用 responseWriter 实例——每个请求 new 一个,否则并发下状态错乱
- ⚠️ Gin 默认的
ResponseWriter是包装过的,直接断言c.Writer.(http.ResponseWriter)会 panic;要用gin.ResponseWriter接口或反射获取底层 writer
性能瓶颈常出在日志写入和全局锁上
用 log.Printf() 直接打统计日志,压测时容易卡住——它锁了全局 stdout,QPS 上千就明显拖慢整个链路。
- ✅ 改用结构化日志库(如
zerolog或zap),开启异步写入模式 - ✅ 统计指标别只打日志,优先写入 Prometheus 的
prometheus.CounterVec或prometheus.HistogramVec,便于聚合分析 - ✅ 耗时单位统一用纳秒(
time.Since()返回time.Duration),上报前转毫秒并四舍五入,避免浮点误差干扰分位数计算 - ⚠️ 不要在中间件里做阻塞操作(如 HTTP 调用、DB 查询)——统计必须轻量,否则它自己就成了性能瓶颈
recovery() 之后、没处理 c.Abort() 场景、也没覆盖 WriteHeader() 这个关键入口。这几个位置一漏,4xx/5xx 和缓存响应就永远进不了统计报表。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











