zap 本身不支持按天切分,所谓 RotateDaily 实为 lumberjack 基于 MaxAge + LocalTime 的时间轮转机制;必须同时设置 MaxAge、MaxBackups、LocalTime:true,且路径稳定、权限正确,首次切分发生在次日首条日志写入时。

为什么 zap 的 RotateDaily 不起作用?
因为 zap 官方本身不提供按天切分功能,所谓 RotateDaily 是社区封装(如 lumberjack)的误传——它实际依赖 lumberjack.Logger 的 MaxAge + LocalTime 组合,且必须显式启用时间轮转逻辑。常见现象是日志始终写入同一个文件,MaxAge: 1 却无任何切割行为。
-
lumberjack默认只按大小(MaxSize)切分,MaxAge和MaxBackups需同时设置才触发时间维度清理 - 必须设
LocalTime: true,否则时间判断基于 UTC,导致本地时区跨天失败 - Fiber 启动后日志实例已固定,修改配置需在
zap.New(...)构建阶段完成,运行时无法热更新
用 lumberjack 配合 zap 实现真·每日切分
核心是让 lumberjack.Logger 精确识别“一天结束”:靠 MaxAge: 1(单位:天)+ LocalTime: true + 每次写日志前检查是否跨天(lumberjack 内部自动做)。注意路径权限和 Windows/Linux 文件锁差异。
w := &lumberjack.Logger{
Filename: "./logs/app.log",
MaxSize: 100, // MB
MaxAge: 1, // 天,必须设
MaxBackups: 30, // 保留最近30天
LocalTime: true, // 关键!否则按UTC算,凌晨可能不切
Compress: true,
}
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(zapcore.EncoderConfig{
TimeKey: "time",
EncodeTime: zapcore.ISO8601TimeEncoder,
LevelKey: "level",
EncodeLevel: zapcore.LowercaseLevelEncoder,
MessageKey: "msg",
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
}),
zapcore.AddSync(w),
zap.InfoLevel,
))
-
Filename必须是绝对路径或确保进程工作目录稳定,Fiber 启动位置影响相对路径解析 -
MaxAge: 1表示“文件超过 1 天就归档”,但首次切分发生在第二天第一个日志写入时,不是 00:00 立刻执行 - Windows 下若进程未释放句柄,旧日志可能无法重命名,建议加
defer w.Close()在 Fiber shutdown 阶段
Fiber 中如何注入并确保日志实例全局可用?
Fiber 的 c.Context 不适合存日志实例(生命周期短、并发不安全),正确方式是初始化一次后挂到 fiber.App 的 Settings.Context 或直接作为包级变量。避免每次请求都新建 zap.Logger 导致 goroutine 泄漏。
- 不要在
app.Use(...)中 new logger,那会为每个中间件创建独立实例 - 推荐在
main()初始化后,用app.Use(func(c *fiber.Ctx) error { c.Locals("logger", logger); return c.Next() })注入,但仅限读取,真正使用仍应从包变量取 - 若用
zap.ReplaceGlobals(),需确认没有其他库(如某些 SDK)偷偷调用zap.L(),否则日志目标错乱
切分后文件名为什么不是 app.log.2024-05-20?
lumberjack 的默认归档名格式是 app.log.2024-05-20T03-27-59.123(含时分秒),不支持纯日期后缀。强行改名会破坏 MaxBackups 清理逻辑,因为它的扫描依赖固定命名模式 ^.*\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}$。
- 若坚持要
app.log.2024-05-20,得自己实现io.WriteCloser,接管写入+重命名+清理,成本远高于接受默认格式 -
Compress: true生成的是.gz文件,归档名末尾追加.gz,解压后内容仍是标准 JSON/Console 格式 - 观察
os.Stat()可发现,主文件app.log始终是当前活跃日志,其余全是历史归档,按名字自然排序即为时间倒序
真正的难点不在切分逻辑,而在确保 lumberjack 的 Rotate 调用时机与本地时区对齐,以及归档文件生命周期管理不被其他进程干扰。别碰命名规则,那是它维持自洽的边界。


















