Gin默认日志不走log包,因其gin.DefaultWriter直接写os.Stdout,绕过标准log;需通过自定义io.Writer或gin.LoggerWithConfig接管,生产环境应设gin.ReleaseMode并替换为异步可控的日志中间件。

为什么 Gin 默认日志不走 log 包?
Gin 内置的 gin.DefaultWriter 是一个 io.Writer,它默认把日志写到 os.Stdout,但**不经过 Go 标准库的 log 包**。这意味着你用 log.SetOutput 或 log.SetFlags 完全不影响 Gin 的日志输出——这是最常见的误解源头。
真正要接管 Gin 日志,得替换它的 gin.LoggerWithWriter 中间件所依赖的 writer,或者直接重写中间件逻辑。
如何用 log 包统一管理 Gin 请求日志?
最稳妥的方式是自己实现一个符合 io.Writer 接口的 wrapper,把 Gin 的日志内容转给 log.Printf 或自定义 handler:
- 不要直接用
log.SetOutput(gin.DefaultWriter)—— 这只会让标准log输出混进 Gin 日志流,无法分离控制 - 推荐新建一个 struct 实现
Write([]byte) (int, error),在其中解析每行(Gin 日志是按行输出的),提取状态码、路径、耗时后调用log.Printf - 注意:Gin 的日志格式固定为
"%s %s %d %s %s %s"(方法、路径、状态码、大小、耗时、用户代理),别依赖正则硬匹配,优先按空格切分 + 索引取值
示例片段:
type LogWriter struct{}
func (lw *LogWriter) Write(p []byte) (n int, err error) {
line := strings.TrimSpace(string(p))
parts := strings.Fields(line)
if len(parts) >= 6 {
status := parts[2]
path := parts[1]
latency := parts[4]
log.Printf("[GIN] %s %s %s %s", parts[0], path, status, latency)
}
return len(p), nil
}
gin.LoggerWithConfig 的关键参数怎么设?
如果你不想完全重写 writer,可以用 gin.LoggerWithConfig 配合自定义 gin.LogWriter,重点看这三个字段:
-
Output:必须是实现了io.Writer的对象,比如os.File、bytes.Buffer,或上节写的LogWriter -
Formatter:可选,用于定制日志字符串格式。默认用gin.DefaultLogFormatter,它返回的是纯文本;如果想输出 JSON,得自己写一个返回fmt.Sprintf(...)的函数 -
Writer:旧版字段,已被Output替代,设了也无效,别填
常见错误:gin.LoggerWithConfig(gin.LoggerConfig{Output: os.Stdout}) 看起来没问题,但如果你同时用了 log.SetOutput(io.MultiWriter(...)),得确保两个 writer 不互相干扰——Gin 日志和业务日志最好走不同 writer 实例。
生产环境要不要关掉 gin.Logger()?
要关,但不是简单删掉中间件。Gin 的 gin.Logger() 在 debug 模式下会打印更详细信息(比如绑定错误),但**即使关闭,它仍会向 gin.DefaultWriter 写少量 panic 日志**。
- 正式部署前,用
gin.SetMode(gin.ReleaseMode),它会自动禁用gin.Logger()的部分输出逻辑 - 更彻底的做法:用
r.Use(customLogger())替换默认中间件,并在customLogger里加开关控制(比如读取环境变量LOG_HTTP) - 注意:关闭后,HTTP 错误(如 404、500)不会自动记录——这部分得你自己在
recovery或全局 error handler 里补上
容易被忽略的一点:Gin 的日志中间件是同步阻塞执行的,高 QPS 下如果 writer 是磁盘文件或网络写入,会拖慢整个请求链路。真要打日志,建议用异步 channel + 后台 goroutine 转发,而不是直接写文件。


















