lumberjack不负责日志分级,仅实现轮转;分级必须由上层日志库(如zap、logrus、slog.handler)在写入前过滤,因其本质是io.writecloser,不解析日志级别。

Zap 本身不负责文件滚动,必须靠 lumberjack.Logger 接管写入,且三件事缺一不可:目录存在、权限正确、os.O_SYNC 或显式 Sync() 调用;否则日志静默丢失,不是“没轮转”,而是根本没写进去。
为什么 lumberjack 配了也不轮转?
常见现象是日志一直追加到单个文件,MaxSize 和 MaxBackups 完全失效。根本原因不是配置错,而是 lumberjack.Logger 启动时静默失败:
- 目录不存在:
os.MkdirAll(filepath.Dir("/var/log/myapp/app.log"), 0755)必须在zap.NewCore()前执行,否则lumberjack打开文件失败但不报错 - 权限不足:运行用户对日志目录无写权限,
lumberjack无法创建新文件,旧文件也不会被压缩或删除 -
MaxSize单位是 MB(不是 KB),设100就是 100MB;若误设为102400(以为是字节),实际变成 102400MB,轮转永远触发不了
zapcore.AddSync 包裹 lumberjack 的硬性条件
lumberjack.Logger 只是带轮转逻辑的 io.WriteCloser,Zap 要让它生效,必须满足底层 IO 行为可控:
- 必须用
zapcore.AddSync()包裹,不能直接传*lumberjack.Logger给zapcore.NewCore()—— 否则 Zap 会跳过同步逻辑,崩溃时最后一段日志必丢 -
lumberjack.Logger初始化时,Filename必须是绝对路径,相对路径在 daemon 化后容易指向错误位置 -
lumberjack.Logger默认不启用os.O_SYNC,需手动设置:Local: true+ 显式调用logger.Sync()(例如在 HTTP handler 结束时),或改用os.O_SYNC | os.O_CREATE | os.O_WRONLY | os.O_APPEND打开底层文件
MaxAge 和 MaxBackups 必须同时配,否则策略失效
这两个参数是独立触发条件,只配一个会导致轮转行为异常:
-
MaxAge: 7 * 24 * time.Hour控制“最老日志保留多久”,但不删文件——除非MaxBackups也设了,否则旧文件堆积不清理 -
MaxBackups: 7控制“最多留几个历史文件”,但不看时间——若MaxAge没设,哪怕文件是半年前的,只要数量没超 7 个,就永远不会删 - 压缩开关
Compress: true只影响MaxBackups超限时的归档文件,不影响当前活跃日志;未压缩的旧文件仍占磁盘空间
生产环境别用 NewDevelopment 配文件输出
开发模式日志含颜色、换行、字段展开,体积大、解析难、CPU 开销高,即使输出到文件也违背结构化日志初衷:
-
zap.NewDevelopment()默认用ConsoleEncoder,字段值会被缩进+换行,ELK / Loki 无法正确提取status或duration - 真正该用的是
zap.NewProduction()或自定义json.Encoder,确保每条日志是单行 JSON,字段名全小写+下划线(如request_id) - 如果容器里要调试,宁可用
json.Encoder+os.Stdout,也不要妥协用NewDevelopment写文件——后者会让日志系统多花 2–3 倍 CPU 去解析无意义的空格和颜色码
最常被忽略的一点:轮转不是“配完就自动跑”,它依赖进程持续运行并定期触发检查;若服务启停频繁、或日志量极低(几天才写满 100MB),lumberjack 的轮转检查可能压根没机会执行——得靠外部 logrotate 或监控补位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











