zap默认不支持按级别拆分文件,因其底层无多文件输出能力,需手动构造多个线程安全的writesyncer并配合levelenablerfunc路由日志;必须禁用采样、隔离句柄、避免共享lumberjack实例,否则会导致混写或丢失。

为什么Zap默认不支持按级别拆分文件
Zap本身是无格式、无轮转、无多文件输出能力的底层日志库,zap.NewProduction() 或 zap.NewDevelopment() 返回的 logger 仅支持单个 WriteSyncer。所谓“按级别拆分”,本质是把不同级别的日志写入不同文件,这需要你手动构造多个 WriteSyncer 并在写入前做级别判断——Zap不内置这个逻辑。
用 zapcore.LevelEnablerFunc 实现动态路由
核心是替换默认的 Core,用 zapcore.NewCore() 手动组装,并配合 zapcore.LevelEnablerFunc 控制每个写入器是否启用。常见错误是直接对同一 WriteSyncer 多次调用 Check() 导致竞态,正确做法是为每个级别准备独立的 WriteSyncer 和对应的 LevelEnablerFunc。
-
zapcore.InfoLevel日志只写入info.log,需传入zapcore.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.InfoLevel }) -
zapcore.ErrorLevel写入error.log,对应zapcore.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel })(注意:含DPanic/Panic/Fatal) - 每个
WriteSyncer必须是线程安全的,推荐用zapcore.AddSync(&os.File{})包裹打开的文件句柄,或用lumberjack.Logger做轮转
避免日志错乱:必须禁用采样并确保同步写入
微服务高并发下,若启用 zap.AddSampler() 或使用异步 WriteSyncer(如未包装 zapcore.AddSync 的管道),会导致级别判断与实际写入脱节——比如 Error 日志被采样后仍触发 error.log 的 Write,但内容却是空或错位。
- 务必移除所有
zap.AddSampler()配置 - 每个文件
*os.File要单独os.OpenFile(..., os.O_CREATE|os.O_WRONLY|os.O_APPEND),不能共用一个句柄 - 最终
Core用zapcore.NewTee(cores...)组合,而非zapcore.NewMultiCore——后者不保证顺序,而Tee会串行调用每个Core的Write
轮转与权限问题:lumberjack + umask 易踩坑点
lumberjack.Logger 默认以 0600 权限创建文件,K8s Pod 或 systemd 服务常因 umask 变更导致日志文件不可读;同时,多个级别共用同一个 lumberjack.Logger 实例会引发 panic(内部锁冲突)。
- 每个级别日志必须用独立的
lumberjack.Logger实例,例如infoLumber := &lumberjack.Logger{Filename: "logs/info.log", ...} - 显式设置
lumberjack.Logger.Mode = 0644,避免依赖环境 umask - 若用
os.OpenFile自建文件,记得调用defer file.Close()——但更推荐lumberjack,它会在轮转时自动关闭旧文件
真正麻烦的不是配置结构,而是确保每个级别路径互斥、句柄隔离、写入同步。漏掉任意一环,都会出现日志混写、文件权限拒绝、或某级别日志完全丢失。











