gin.default()不能直接支持日志分割,因其内置logger中间件仅写入os.stdout,不接受自定义writesyncer,无法接入lumberjack等轮转写入器;必须改用gin.new()配合zap等自定义中间件实现。

为什么 gin.Default() 不能直接支持日志分割
因为 gin.Default() 内部调用的是 gin.Logger() 和 gin.Recovery(),这两个中间件只往 os.Stdout 写日志,不接受自定义 WriteSyncer,也没法接入 lumberjack.Logger 这类带切割能力的写入器。
想做日志分割,必须绕过 gin.Default(),改用 gin.New() + 自定义中间件。核心就是把 Zap 的 core 指向一个支持轮转的 WriteSyncer,比如 lumberjack.Logger。
-
lumberjack.Logger不是标准io.Writer,需用zapcore.AddSync()包一层才能传给zapcore.NewCore() - 别漏掉
defer logger.Sync()—— 否则缓冲区里最后几条日志可能丢失 -
lumberjack的MaxSize单位是字节,不是 MB;设成1048576才是 1MB,不是1
如何用 lumberjack 实现按大小+日期双维度分割
Zap 本身不负责分割,靠 lumberjack 实现文件管理。但要注意:它只按大小切,不按日期;想同时满足“每天一个文件”和“单个文件不超过 10MB”,得组合使用 —— 比如用日期生成文件名前缀,再交给 lumberjack 管理单个文件大小和备份。
典型路径配置:"./logs/access-{2006-01-02}.log" 是无效的,lumberjack 不解析时间模板。正确做法是每天程序启动时动态生成带日期的文件名,或用外部定时任务配合。
- 推荐方案:固定文件名(如
"access.log"),靠lumberjack的MaxSize+MaxAge控制生命周期 - 如果真要按天分文件,得自己封装一个
WriteSyncer,每天零点切换os.File句柄(注意并发安全) -
lumberjack的Compress: true会启用 gzip 压缩归档,但仅对已轮转的旧文件生效,主日志文件始终是明文
gin.Logger() 替换为 zap 中间件的关键参数
原生 gin.Logger() 输出格式是纯文本、无结构、不带 traceID。换成 Zap 后,要复现甚至增强它的行为,关键在 zapcore.EncoderConfig 和中间件逻辑。
例如,gin.Logger() 默认打印耗时、状态码、路径;Zap 中间件必须手动提取这些字段并塞进 logger.Info() 或 logger.With() 调用里。
- 必须设置
EncodeCaller: zapcore.ShortCallerEncoder,否则 caller 字段是空的 -
AddCallerSkip(1)要加,不然 caller 总显示在中间件函数内部,而不是业务 handler 里 - 别用
zap.NewDevelopment()上生产 —— 它默认输出彩色文本、不压缩 JSON,且 level 判断逻辑和NewProduction()不同 - HTTP 错误日志建议用
logger.Error(),但 Recovery 中间件捕获 panic 后,需显式调用logger.With(zap.String("stack", string(stack))).Error("panic recovered")
多日志类型(access/error/run)共存时的常见冲突
项目里常需要 access 日志、error 日志、SQL 日志分目录存放。Zap 支持 zapcore.NewTee() 合并多个 core,但要注意:所有 core 共享同一个 encoder 配置,而不同日志类型往往需要不同字段(比如 access 要 latency,error 要 stacktrace)。
硬拼 NewTee 容易导致字段冗余或缺失。更稳妥的做法是为每类日志初始化独立 *zap.Logger 实例,各自配专属 core 和 encoder。
- 每个
*zap.Logger都要配独立lumberjack.Logger实例,不能复用 —— 否则文件句柄冲突、写入错乱 - 全局 logger 实例建议用
*zap.SugaredLogger,它比*zap.Logger更轻量,适合高频写入场景(如 access 日志) - 别在 HTTP handler 里反复调用
logger.With(...)创建新 logger —— 每次都分配 map,压测时 GC 压力明显;应提前构造好带固定字段的子 logger
日志分割真正难的不是配置,而是多实例间文件句柄管理、panic 时的 flush 保底、以及跨 goroutine 的 context 透传 —— 这些地方一不留神就丢日志。











