如何优雅配置带日志轮转的Zap Logger并合并测试函数
问题描述
- 函数
Test1()使用官方推荐的日志轮转切割库gopkg.in/natefinch/lumberjack.v2。 - 函数
Test2()基于官方基础配置,通过YAML文件读取配置初始化Logger。
执行main函数后:
控制台输出:2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"}
日志文件foo.log输出:{"level":"info","ts":1684111756.5545945,"msg":"logger construction succeeded:lumberjack.Logger"}
两者输出格式存在明显差异。
当前需求
- 支持通过配置文件
config_log_zap.yaml使所有配置生效,由lumberjack完成日志轮转切割工作。 - 控制台与日志文件输出内容完全一致,便于通过配置文件快速调整输出格式。开发时需同时输出到控制台并记录历史日志,预期输出如下:
控制台输出:2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"}
日志文件foo.log输出:2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"} - 将
Test1()与Test2()合并为单个函数Test0()以满足上述两项需求。
解决方案
核心思路是基于YAML配置构建Logger,同时将文件输出目标替换为lumberjack的轮转Writer,确保所有输出目标使用统一的编码器配置。
修改后的main.go代码如下:
package main import ( "gopkg.in/yaml.v3" "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) func Test0() { // 读取YAML配置文件 var cfg zap.Config yamlFile, err := os.ReadFile("./config_log_zap.yaml") if err != nil { panic(err) } if err := yaml.Unmarshal(yamlFile, &cfg); err != nil { panic(err) } // 基于配置生成统一编码器,保证控制台和文件输出格式一致 encoder, err := cfg.BuildEncoder() if err != nil { panic(err) } // 处理输出目标:替换文件路径为lumberjack Writer,保留标准输出 var writeSyncers []zapcore.WriteSyncer for _, path := range cfg.OutputPaths { switch path { case "stdout": writeSyncers = append(writeSyncers, zapcore.Lock(os.Stdout)) case "stderr": writeSyncers = append(writeSyncers, zapcore.Lock(os.Stderr)) default: // 为文件路径配置lumberjack日志轮转 w := zapcore.AddSync(&lumberjack.Logger{ Filename: path, MaxSize: 500, // 单文件最大容量(MB) MaxBackups: 3, // 保留备份文件数量 MaxAge: 28, // 日志文件最大保留天数 }) writeSyncers = append(writeSyncers, w) } } // 合并所有输出目标 multiWriter := zapcore.NewMultiWriteSyncer(writeSyncers...) // 处理错误输出路径(同样应用lumberjack轮转) var errWriteSyncers []zapcore.WriteSyncer for _, path := range cfg.ErrorOutputPaths { switch path { case "stdout": errWriteSyncers = append(errWriteSyncers, zapcore.Lock(os.Stdout)) case "stderr": errWriteSyncers = append(errWriteSyncers, zapcore.Lock(os.Stderr)) default: w := zapcore.AddSync(&lumberjack.Logger{ Filename: path, MaxSize: 500, MaxBackups: 3, MaxAge: 28, }) errWriteSyncers = append(errWriteSyncers, w) } } errMultiWriter := zapcore.NewMultiWriteSyncer(errWriteSyncers...) // 构建日志核心 core := zapcore.NewCore(encoder, multiWriter, cfg.Level) // 应用配置中的其他选项 logger := zap.New(core) if cfg.Development { logger = logger.WithOptions(zap.Development()) } if !cfg.DisableCaller { logger = logger.WithOptions(zap.AddCaller()) } if !cfg.DisableStacktrace { logger = logger.WithOptions(zap.AddStacktrace(cfg.Level)) } // 添加初始字段 if len(cfg.InitialFields) > 0 { fields := make([]zap.Field, 0, len(cfg.InitialFields)) for k, v := range cfg.InitialFields { fields = append(fields, zap.Any(k, v)) } logger = logger.With(fields...) } // 应用采样配置 if cfg.Sampling != nil { logger = logger.WithOptions(zap.WrapCore(func(c zapcore.Core) zapcore.Core { return zapcore.NewSamplerWithOptions(c, cfg.Sampling.Initial, cfg.Sampling.Thereafter) })) } defer logger.Sync() logger.Info("logger construction succeeded: merged config with lumberjack") } func main() { Test0() }
关键细节说明
- 统一编码器:使用
cfg.BuildEncoder()直接基于YAML配置生成编码器,确保控制台和文件输出格式完全一致。 - 输出目标替换:遍历配置中的输出路径,将文件路径替换为
lumberjack的Writer,保留标准输出的原生处理逻辑。 - 多输出合并:通过
NewMultiWriteSyncer将控制台和文件输出合并,实现同一份日志同时输出到多个目标。 - 完整配置兼容:保留YAML配置中的所有选项(开发模式、初始字段、采样、错误日志等),确保配置完全生效。
执行Test0()后,控制台和foo.log的输出格式将完全匹配,同时日志文件由lumberjack自动完成轮转切割。
内容的提问来源于stack exchange,提问作者jiaopengzi
相关产品推荐
相关产品推荐

