必须在next.ServeHTTP()前执行start := time.Now()并用defer包裹time.Since(start)计算耗时,否则无法捕获真实处理时间;需包装ResponseWriter以准确获取状态码和响应字节数。

Go HTTP中间件里怎么用time.Now()和defer算准耗时
必须在调用next.ServeHTTP()前执行start := time.Now(),再用defer包裹耗时计算逻辑——这是唯一能保证time.Since(start)拿到真实处理耗时的方式。如果把defer写在next.ServeHTTP()之后,它会在函数返回前才注册,但此时next早已执行完,defer里的time.Since()就失去了意义。
常见错误包括:
- 在
defer里直接引用外部变量start(Go 1.22 之前会捕获地址而非值,导致取到空time.Time{}) - 用
time.Now().Sub(start)手动减法,而非time.Since(start),可能因时钟倒退出负值 - 把
start定义在defer闭包内部,造成每次调用都重置
正确写法示例:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func(s time.Time) {
log.Printf("cost: %v", time.Since(s))
}(start)
next.ServeHTTP(w, r)
})
}
为什么必须包装ResponseWriter才能拿到真实状态码
原生http.ResponseWriter不暴露Status()方法,你无法在next.ServeHTTP()结束后读取实际返回的状态码——w.WriteHeader()可能被下游 handler 多次调用,或根本没调用(比如 panic 导致 500 但未显式写头)。不包装就只能默认记 200,日志完全失真。
实操要点:
- 实现一个轻量
responseWriter结构体,嵌入http.ResponseWriter并重写WriteHeader() - 在
WriteHeader()里更新statusCode字段,确保只记最后一次有效写入 - 务必把包装后的
rw传给next.ServeHTTP(rw, r),否则状态码捕获失效 - 别在
WriteHeader()里做耗时操作(如打日志),避免阻塞响应流
日志字段怎么选才查得快、不影响性能
字段不是越多越好,关键字段必须稳定、可索引、无歧义:至少包含method、path、status、cost_ms、remote_addr。用fmt.Sprintf拼接比多次log.Printf调用快,也避免log.SetFlags(log.Lshortfile)这类带锁操作拖慢高并发场景。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
容易踩的坑:
- 用
time.Now().Format()生成时间戳——它在高并发下有锁竞争,改用start.Format()复用起始时间 - 把
r.Header.Get("User-Agent")等大字符串无条件写入日志,可能撑爆磁盘或触发 GC 压力 - 日志输出到文件时没调
os.Stderr或没手动Flush(),导致日志延迟可见 - 漏掉
X-Forwarded-For解析,所有请求 IP 都记成反向代理地址
采样和并发安全怎么兼顾
全量打日志在 QPS 过万时会显著拖慢服务,但采样逻辑本身不能成为瓶颈。别用math/rand加锁生成随机数,它不是并发安全的;也别在每次请求里新建rand.New,开销太大。
推荐做法:
- 对
r.URL.Path + r.RemoteAddr做fnv哈希,再对采样率分母取模,结果稳定可复现 - 采样率用
float64配置(如sampleRate = 0.01),避免整型除法截断 - 耗时指标(非完整日志)可以全量上报 Prometheus,用
promhttp暴露http_request_duration_seconds,比文本日志更利于聚合分析 - 如果用了
gzip中间件,确保耗时统计放在它前面,否则测的是压缩后传输耗时,不是真实 handler 执行时间
最常被忽略的一点:系统时钟漂移。虚拟机休眠恢复、NTP 同步都可能导致time.Now()倒退,哪怕用了time.Since(),只要start来自旧时间源或跨进程传递,仍可能出负值。生产环境建议监控http_request_duration_seconds_count中耗时
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










