引言

设计日志包,干货

一直对日志的包装非常好奇,但是不会写,下面就随便讲一下我抄的这个代码

(下面代码纯属是我抄过来的,学习学习)


整体代码

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package log

import (
"github.com/natefinch/lumberjack"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"tikapp/common/config"
"time"
)

// Logger 整个项目的Logger
var Logger *zap.Logger

func Init() {
if config.AppCfg.RunMode == "debug" {
// 开发模式 日志输出到终端
core := zapcore.NewTee(
zapcore.NewCore(getEncoder(),
zapcore.Lock(os.Stdout), zapcore.DebugLevel),
)
Logger = zap.New(core, zap.AddCaller())
} else {
fileLog()
}
}

func fileLog() {
// 调试级别
debugPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.DebugLevel
})
// 日志级别
infoPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.InfoLevel
})
// 警告级别
warnPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.WarnLevel
})
// 错误级别
errorPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.ErrorLevel
})
// panic级别
panicPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.PanicLevel
})
// fatal级别
fatalPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.FatalLevel
})

cores := [...]zapcore.Core{
getEncoderCore("./debug.log", debugPriority),
getEncoderCore("./info.log", infoPriority),
getEncoderCore("./warn.log", warnPriority),
getEncoderCore("./error.log", errorPriority),
getEncoderCore("./panic.log", panicPriority),
getEncoderCore("./fatal.log", fatalPriority),
}

// zap.AddCaller() 可以获取到文件名和行号
Logger = zap.New(zapcore.NewTee(cores[:]...), zap.AddCaller())
}

func pathExists(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}

func getLogWriter(fileName string) zapcore.WriteSyncer {
dir, _ := os.Getwd() // 获取项目目录
sperator0 := os.PathSeparator
sperator := string(sperator0)
// log 存放路径
dir = dir + sperator + "runtime" + sperator + "logs"
if !pathExists(dir) {
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
logrus.Warnf("create dir %s failed", dir)
}
}
lumberJackLogger := &lumberjack.Logger{
Filename: dir + sperator + fileName, // 日志文件路径
MaxSize: 5, // 设置日志文件最大尺寸
MaxBackups: 5, // 设置日志文件最多保存多少个备份
MaxAge: 30, // 设置日志文件最多保存多少天
Compress: true, // 是否压缩 disabled by default
}
// 返回同步方式写入日志文件的zapcore.WriteSyncer
return zapcore.AddSync(lumberJackLogger)
}

func getEncoderCore(fileName string, level zapcore.LevelEnabler) (core zapcore.Core) {
writer := getLogWriter(fileName)
return zapcore.NewCore(getEncoder(), writer, level)
}

func getEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(getEncoderConfig())
}

func getEncoderConfig() (config zapcore.EncoderConfig) {
config = zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
TimeKey: "time",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder, // 将日志级别字符串转化为小写
EncodeTime: customTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder, // 执行消耗时间转化成浮点型的秒
EncodeCaller: zapcore.ShortCallerEncoder, // 以包/文件:行号 格式化调用堆栈
}
return config
}

// CustomTimeEncoder 自定义日志输出时间格式
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format(config.LogCfg.TimeFormat))
}

整体的思路是

  1. 开发模式 Init中的第一个if
  2. 非开发模式 else后面

整体的代码很多呈现都是关于非开发模式的行为模式

对于开发模式中的代码少得可怜。。

拆分代码

下面用开发模式 / 非开发模式

两部分的代码来讲解

开发模式

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
// 判断为开发模式
func Init(){
if config.AppCfg.RunMode == "debug" {
// 开发模式 日志输出到终端
core := zapcore.NewTee(
zapcore.NewCore(getEncoder(),
zapcore.Lock(os.Stdout), zapcore.DebugLevel),
)
Logger = zap.New(core, zap.AddCaller())
}
}
// 把需要结合成core的参数生成一下
// Encoder这边用了点封装,写在一起也没啥其实
func getEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(getEncoderConfig())
}

func getEncoderConfig() (config zapcore.EncoderConfig) {
config = zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
TimeKey: "time",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder, // 将日志级别字符串转化为小写
EncodeTime: customTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder, // 执行消耗时间转化成浮点型的秒
EncodeCaller: zapcore.ShortCallerEncoder, // 以包/文件:行号 格式化调用堆栈
}
return config
}

// CustomTimeEncoder 自定义日志输出时间格式
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format(config.LogCfg.TimeFormat))
}

就这样,会发现开发模式其实只是设置了一个Encoder的config配置,其他真的没了

因为开发模式时最好将日志直接输出到os.stdout,级别默认Debug级别

zapcore.NewTee()

NewTee 是根据core个数来返回不同的core,如果有多个core就会叠加

zapcore.AddCaller()

AddCaller 是会在日志中加上调用者的信息

func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
xxxxx
}

这个函数点入源码会发现他是个函数类型,很有意思

非开发模式

这个就麻烦一点了

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
func Init() {
else {
fileLog()
}
}
// 不同的级别放入不同的文件中
func getLogWriter(fileName string) zapcore.WriteSyncer {
dir, _ := os.Getwd() // 获取项目目录
sperator0 := os.PathSeparator
sperator := string(sperator0)
// log 存放路径
dir = dir + sperator + "runtime" + sperator + "logs"
if !pathExists(dir) {
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
logrus.Warnf("create dir %s failed", dir)
}
}
lumberJackLogger := &lumberjack.Logger{
Filename: dir + sperator + fileName, // 日志文件路径
MaxSize: 5, // 设置日志文件最大尺寸
MaxBackups: 5, // 设置日志文件最多保存多少个备份
MaxAge: 30, // 设置日志文件最多保存多少天
Compress: true, // 是否压缩 disabled by default
}
// 返回同步方式写入日志文件的zapcore.WriteSyncer
return zapcore.AddSync(lumberJackLogger)
}
// 获取core的方式封装一下
func getEncoderCore(fileName string, level zapcore.LevelEnabler) (core zapcore.Core) {
writer := getLogWriter(fileName)
return zapcore.NewCore(getEncoder(), writer, level)
}
// 把core融合在一起,最后生成一个Logger
func fileLog() {
// 调试级别
debugPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.DebugLevel
})
// 日志级别
infoPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.InfoLevel
})
// 警告级别
warnPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.WarnLevel
})
// 错误级别
errorPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.ErrorLevel
})
// panic级别
panicPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.PanicLevel
})
// fatal级别
fatalPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
return lev == zap.FatalLevel
})

cores := [...]zapcore.Core{
getEncoderCore("./debug.log", debugPriority),
getEncoderCore("./info.log", infoPriority),
getEncoderCore("./warn.log", warnPriority),
getEncoderCore("./error.log", errorPriority),
getEncoderCore("./panic.log", panicPriority),
getEncoderCore("./fatal.log", fatalPriority),
}

// zap.AddCaller() 可以获取到文件名和行号
Logger = zap.New(zapcore.NewTee(cores[:]...), zap.AddCaller())
}

fileLog函数负责在非开发模式下的日志配置。它首先定义了不同日志级别对应的启用函数,然后创建了一个lumberjack.Logger实例,用于管理日志文件的滚动和压缩等。接着,它为每种日志级别创建了一个zapcore.Core实例,并将这些核心合并成一个zapcore.Tee,再通过zap.New创建最终的zap.Logger实例。

总结

代码看了很久,感觉完全没必要,毕竟Any说面试不考这个

但是就是对这个感兴趣,他们写的代码太优雅了。

问题:其实这个Logger还是暴露在外面。