gin 默认日志无法直接替换为zap,因其logger中间件硬编码依赖log包并写入os.stdout;正确接入需同时实现zap中间件(捕获耗时、状态码等字段)和全局zap logger实例,并重写recovery处理panic日志。

为什么 Gin 默认日志不能直接替换为 Zap
Gin 的 gin.Default() 和 gin.New() 内部硬编码使用了 gin.Logger() 中间件,它依赖 log 包并直接写入 os.Stdout,不提供接口注入自定义 logger。强行替换标准库 log 输出目标(比如用 log.SetOutput())会影响所有包,且无法控制字段结构、采样、异步写入等 Zap 特性。
正确接入 Zap 的两种方式:中间件 + 全局 logger
必须同时做两件事:一是用 Zap 实现一个兼容 Gin 中间件签名的 gin.HandlerFunc;二是把 Zap 实例暴露为全局变量或通过依赖注入传递,供业务代码调用。不能只换中间件而留着 log.Printf 这类调用。
- 中间件需捕获请求耗时、状态码、路径、客户端 IP,并按 Zap 字段格式记录(如
zap.String("path", c.Request.URL.Path)) - 全局 logger 推荐用
zap.L()或显式声明*zap.Logger变量,避免在 handler 里反复调用zap.NewProduction() - 务必调用
logger.Sync()(尤其在main()退出前),否则最后几条日志可能丢失
Zap 日志中间件常见错误写法
直接把 zap.Info() 塞进中间件最外层,会导致每请求都新建 logger 实例,性能暴跌;或者漏掉 c.Next(),导致后续 handler 不执行;又或者用 zap.String("body", string(body)) 读取 request body 后没重置 c.Request.Body,导致下游解析失败。
- 错误示例:
zap.L().Info("request start")—— 没传上下文字段,也没测耗时,等于白换 - 错误示例:
zap.NewExample().Info("xxx")—— 每次 new,内存和 goroutine 泄漏风险高 - 正确做法:在中间件开头记录开始时间,
c.Next()后计算耗时,统一用logger.Info()写一条结构化日志
如何让 Zap 与 Gin 的 error 日志对齐
Gin 在 panic 时会调用 gin.Recovery() 并打印堆栈,默认走 log。这个环节不会触发你写的 Zap 中间件,必须手动替换 gin.RecoveryWithWriter() 的 writer,或更稳妥地——重写 gin.Recovery() 逻辑,用 zap.Error() 记录 panic 信息。
- 不要用
gin.RecoveryWithWriter(zapWriter),Zap 没有直接实现io.Writer的日志输出器 - 推荐方式:复制
gin.Recovery()源码,在recover()分支里调用logger.Error("panic recovered", zap.String("stack", stackStr)) - 注意:panic 日志要包含完整堆栈(用
debug.Stack())、请求路径、query 参数(可选),但别打 request body,防止敏感信息泄露
client_ip),避免跟 Gin 默认日志字段(status vs statusCode)冲突。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











