Go日志轮转方案
Reverse Lv4

上代码

直接调用NewLogger函数初始化日志实例就行,参数是日志的保存位置,随便改改就能用了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package logger

import (
"os"
"time"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)

// Logger 封装 zap.SugaredLogger,提供带轮转功能的日志记录器
type Logger struct {
// sugar 是 zap 提供的语法糖 logger,支持 printf 风格的格式化输出 (Info, Infof 等)
sugar *zap.SugaredLogger
// rotator 保存 lumberjack 实例,用于在 Close 时显式关闭文件句柄
rotator *lumberjack.Logger
}

// NewLogger 初始化日志模块
// logDir: 日志文件保存的目录 (例如 "./logs")
func NewLogger(logDir string) (*Logger, error) {
// 1. 配置 Lumberjack (日志切割/轮转核心)
// 它是 zap 的底层输出目标 (WriteSyncer)
rotator := &lumberjack.Logger{
Filename: logDir + "/app.log", // 日志文件的完整路径
MaxSize: 10, // 单个日志文件的最大体积,单位:MB
MaxBackups: 30, // 最多保留的历史日志文件个数 (旧文件会被删除)
MaxAge: 30, // 最多保留的历史日志天数 (旧文件会被删除)
Compress: true, // 是否压缩旧日志 (保存为 .gz 格式以节省空间)
LocalTime: true, // 备份文件名是否使用本地时间 (默认是 UTC)
}

// 2. 配置 Zap 的日志编码格式 (Encoder)
// 这里决定了日志在文件中长什么样
encoderConfig := zapcore.EncoderConfig{
TimeKey: "time", // 时间字段的 key 名
LevelKey: "level", // 级别字段的 key 名
NameKey: "logger", // logger 名称字段
CallerKey: "caller", // 调用者代码行号字段
MessageKey: "msg", // 消息内容字段
StacktraceKey: "stacktrace", // 堆栈跟踪字段
LineEnding: zapcore.DefaultLineEnding, // 换行符 (\n)
EncodeLevel: zapcore.CapitalLevelEncoder, // 级别格式: 大写 (INFO, ERROR)
EncodeTime: customTimeEncoder, // 时间格式: 自定义函数 (见下方)
EncodeDuration: zapcore.SecondsDurationEncoder, // 执行消耗时间格式
EncodeCaller: zapcore.ShortCallerEncoder, // 调用者格式: 包名/文件名:行号
}

// 3. 创建 Core (核心逻辑)
// NewMultiWriteSyncer 允许我们将日志同时输出到 [文件] 和 [控制台]
writeSyncer := zapcore.NewMultiWriteSyncer(
zapcore.AddSync(rotator), // 输出到文件 (带轮转)
zapcore.AddSync(os.Stdout), // 输出到控制台 (方便开发调试)
)

core := zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig), // 使用 Console 编码器 (人类可读性更好,不像 JSON 那么乱)
writeSyncer, // 输出位置
zapcore.InfoLevel, // 日志级别 (Info 及以上才记录)
)

// 4. 构建 Logger 实例
// AddCaller: 显示日志是哪一行代码打出来的
// AddCallerSkip(1): 关键点!因为我们封装了一层 Logger 结构体,
// 如果不跳过 1 层,所有日志显示的行号都会是 logger.go 中的 Info 函数,而不是业务代码的位置。
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))

return &Logger{
sugar: zapLogger.Sugar(), // 使用 Sugar 模式,支持 Infof 这种 printf 风格
rotator: rotator,
}, nil
}

// customTimeEncoder 自定义时间格式化 (例如: 2025-12-14 22:30:05.123)
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}

// Info 对应原有的 Info 方法,支持 formatting
func (l *Logger) Info(format string, v ...interface{}) {
// Infof 内部自动处理 fmt.Sprintf,不需要你手动 format
l.sugar.Infof(format, v...)
}

// Error 对应原有的 Error 方法
func (l *Logger) Error(format string, v ...interface{}) {
l.sugar.Errorf(format, v...)
}

// Warn 对应原有的 Warn 方法
func (l *Logger) Warn(format string, v ...interface{}) {
l.sugar.Warnf(format, v...)
}

// Close 在程序退出时调用,确保缓冲区日志刷入磁盘
func (l *Logger) Close() {
// Sync 刷新缓冲区
_ = l.sugar.Sync()
// 关闭文件句柄
if l.rotator != nil {
_ = l.rotator.Close()
}
}