最常见情况是中间件函数未挂载到路由或handler上:Go标准库http.ServeMux不支持中间件概念,传裸中间件函数会编译失败,须显式包装如handler := LoggingMiddleware(http.HandlerFunc(myHandler))再注册。

中间件没注册到请求链路里
最常见的情况是写了中间件函数,但压根没挂到路由或 handler 上。Go 标准库 http.ServeMux 不支持中间件概念,http.Handle 和 http.HandleFunc 都只接受 http.Handler 或 func(http.ResponseWriter, *http.Request),你传一个裸的中间件函数(比如 LoggingMiddleware)进去,编译都过不了。
正确做法是显式包装:用中间件函数包裹最终 handler,再注册:
handler := LoggingMiddleware(http.HandlerFunc(myHandler))
http.Handle("/api", handler)
如果你用的是 Gin、Fiber、Echo 等框架,必须确认调用的是 Use(全局)或 UseAt(分组),而不是 GET/POST —— 后者注册的是业务 handler,不是中间件。
- Gin 中写
r.GET("/path", middleware, handler)是错的;应写r.Use(middleware)或group.Use(middleware) - Fiber 中
app.Use()必须在app.Get()之前,否则 OPTIONS 请求等预检路径可能绕过 - 用
net/http自建服务时,别漏掉next.ServeHTTP(w, r),否则整个链路就断了
ResponseWriter 没包装导致状态码/响应体捕获失败
想记录真实 HTTP 状态码,却总打出来 200?大概率是你没包装 http.ResponseWriter。标准库的 w.WriteHeader(status) 是幂等的,但中间件里直接读 w.Header().Get("Status") 拿不到,因为 Header 不存状态码;而 w.Status() 是 Gin 等框架扩展的方法,原生 http.ResponseWriter 根本没有。
必须自己实现一个包装类型,重写 WriteHeader 和 Write 方法:
type responseWriterWrapper struct {
http.ResponseWriter
statusCode int
}
func (w *responseWriterWrapper) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
然后在中间件里用它替换原始 w,再传给 next.ServeHTTP。否则日志里的 status 就是默认值(通常是 0 或 200)。
- 别在中间件里用
log.Printf("%d", w.WriteHeader(200))—— 这会提前触发写头,下游 handler 再写就 panic - 如果用了第三方日志库(如 zap),注意它默认不带
StatusCode字段,得手动从 wrapper 取 - 某些代理(如 Nginx)会覆盖状态码,此时 wrapper 捕获的是 Go 侧发出的码,和最终客户端收到的可能不一致
RequestURI 被误读或敏感字段未脱敏
日志里路径总显示 / 或 /favicon.ico,不是你预期的 /api/users?问题常出在字段选择上。r.URL.Path 在反向代理、URL 重写、甚至某些测试工具下会被改写,而 r.RequestURI 是服务端收到的原始字符串,更可靠。
但它带查询参数,比如 /login?token=abc123 —— 这种不能直接进日志,否则审计过不了。
- 用正则或
strings.Split(r.RequestURI, "?")截断参数部分,再对 path 做白名单匹配脱敏(如/api/payments/*→/api/payments/[id]) - 别用
r.URL.String(),它会补?、丢 fragment,且已被标准库解析过,不可逆 - 如果用了
gorilla/mux,mux.CurrentRoute(r).GetName()可补充路由名,但必须加if route := mux.CurrentRoute(r); route != nil防空指针
日志缓冲未刷新或 goroutine 提前退出
服务一重启,最后几条日志就没了;或者并发高时日志断续、乱序?这和 Go log 包底层行为有关。标准 log.Println 是同步写,但依赖 OS 缓冲 —— 如果程序 panic 或快速退出,缓冲区没 flush,日志就丢了。
两种典型场景:
- 在中间件里起 goroutine 异步打日志(比如发到 Kafka),但主流程没等它结束,goroutine 被强制终止
- 用
log.SetOutput(bufio.NewWriter(os.Stdout))加了缓冲,却忘了在os.Exit或signal.Notify里调log.Writer().(*bufio.Writer).Flush()
生产环境建议:要么用 zap/lumberjack 这类自带缓冲控制和文件切割的库,要么在自研日志中加 defer writer.Flush() 和 signal.Notify 捕获 os.Interrupt / syscall.SIGTERM。
真正难搞的不是“怎么记”,是“怎么确保它一定落盘”。尤其在容器环境下,stdout 重定向 + 日志采集器(如 fluentd)+ 应用退出顺序,三者稍有不一致,日志就消失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











