You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优雅配置带日志轮转的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"}

两者输出格式存在明显差异。

当前需求
  1. 支持通过配置文件config_log_zap.yaml使所有配置生效,由lumberjack完成日志轮转切割工作。
  2. 控制台与日志文件输出内容完全一致,便于通过配置文件快速调整输出格式。开发时需同时输出到控制台并记录历史日志,预期输出如下:
    控制台输出: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"}
  3. 将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()
}

关键细节说明

  1. 统一编码器:使用cfg.BuildEncoder()直接基于YAML配置生成编码器,确保控制台和文件输出格式完全一致。
  2. 输出目标替换:遍历配置中的输出路径,将文件路径替换为lumberjack的Writer,保留标准输出的原生处理逻辑。
  3. 多输出合并:通过NewMultiWriteSyncer将控制台和文件输出合并,实现同一份日志同时输出到多个目标。
  4. 完整配置兼容:保留YAML配置中的所有选项(开发模式、初始字段、采样、错误日志等),确保配置完全生效。

执行Test0()后,控制台和foo.log的输出格式将完全匹配,同时日志文件由lumberjack自动完成轮转切割。

内容的提问来源于stack exchange,提问作者jiaopengzi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 04:02:03