引言

我现在好困呐,明明现在才十二点半,困死了

之前一直想着别人写的日志怎么这么高大上,自己写的怎么四五行就结束了——定制化

在小项目中其实自己随便用用就够了,但是项目规模一大,就需要吧日志也定制一下,毕竟定制版肯定比简陋版要快乐一点

定制化

go get -u go.uber.org/zap

设置

  • 配置设置
  • 代码设置

代码将从这两方面进行定制,配置设置中的定制指的是某些输出,显示的定制,而代码设置这是日志的结构之类的设置,因此我们需要两个文件(如何读入配置这里不再赘述)

  • config.go
  • zap.go

思路

下面将从定制的代码逻辑开始讲解,其实相比于模块化的代码呈现和逻辑性的代码梳理,其实前者更容易被观摩,后者更容易理清楚思路

Config

本来觉得先从core开始会比较好,现在感觉还是从config开始比较好

1
2
3
4
5
6
7
8
9
10
11
type Zap struct {
Prefix string `default:"gin-vue-admin" yaml:"prefix" json:"prefix"` // 时间前缀
Level string `default:"panic" yaml:"level" json:"level"` // log等级
Path string `yaml:"path" json:"path"`
Director string `default:"director" yaml:"director" json:"director"` // 路径噢
EncoderLevel string `default:"LowercaseLevelEncoder" yaml:"encoder_level" json:"encoder_level"`// 大小写编码
MaxAge int `default:"7" yaml:"max_age" json:"max_age"` // 持续时间,lumberjack可以使用
ShowLine bool `default:"true" yaml:"show_line" json:"show_line"` // 是否显示行
LogInConsole bool `default:"true" yaml:"log_in_console" json:"log_in_console" ` //是否在终端输出
Encoder string `default:"console" yaml:"encoder" json:"encoder"` // json/console 形式输出
}

下面还要进行一些转换,方便后面的代码书写主要

  • 把 Level string 类型 转化为 zapcore.level 类型
  • 把 EncoderLevel 类型 转化为 zapcore.LevelEncoder 类型
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
func (z *Zap) GetLevelEncoder() zapcore.LevelEncoder {
switch {
case z.EncoderLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
return zapcore.LowercaseLevelEncoder
case z.EncoderLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
return zapcore.LowercaseColorLevelEncoder
case z.EncoderLevel == "CapitalLevelEncoder": // 大写编码器
return zapcore.CapitalLevelEncoder
case z.EncoderLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
return zapcore.CapitalColorLevelEncoder
default:
return zapcore.LowercaseLevelEncoder
}
}

func (z *Zap) TransmitLvl() zapcore.Level {
switch z.Level {
case "debug":
return zapcore.DebugLevel
case "Info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "Dpanic":
return zapcore.DPanicLevel
case "panic":
return zapcore.PanicLevel
default:
return zapcore.DebugLevel
}
}

核心

一切的万恶资源都来自于这个方法,他让我们明白我们需要准备什么

  • encoder 配置器
  • WriteSyncer 写入器
  • levelEnabler 表示有哪些特定级别会被记录下来
1
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core 

Encoder

这是配置器的配置,我们可以用这些配置配置自己需要的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type EncoderConfig struct {
MessageKey string `json:"messageKey" yaml:"messageKey"`
LevelKey string `json:"levelKey" yaml:"levelKey"`
TimeKey string `json:"timeKey" yaml:"timeKey"`
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` // 一些key 键
SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"` // 是否跳过行尾
LineEnding string `json:"lineEnding" yaml:"lineEnding"` // 行尾用什么呈现
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` // 编码大小写,就是输出形式是全大写,全小写,还是其他。。
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` // 时间格式
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` // 持续时间,精确值
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` // 记录调用者的信息
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// ....
}
  • 总定制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
    return zapcore.EncoderConfig{
    MessageKey: "msg",
    LevelKey: "level",
    TimeKey: "ts",
    NameKey: "logger",
    CallerKey: "caller",
    StacktraceKey: "stacktrace",
    LineEnding: zapcore.DefaultLineEnding, // 行尾为 '/n'
    EncodeLevel: Z.GetLevelEncoder(),
    EncodeTime: Z.CustomTimeEncoder,
    EncodeDuration: zapcore.SecondsDurationEncoder, // 精确到秒
    EncodeCaller: zapcore.FullCallerEncoder, // 详细调用者信息
    }
    }
  • 大小写编码配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    func (z *Zap) GetLevelEncoder() zapcore.LevelEncoder {
    switch {
    case z.EncoderLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
    return zapcore.LowercaseLevelEncoder
    case z.EncoderLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
    return zapcore.LowercaseColorLevelEncoder
    case z.EncoderLevel == "CapitalLevelEncoder": // 大写编码器
    return zapcore.CapitalLevelEncoder
    case z.EncoderLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
    return zapcore.CapitalColorLevelEncoder
    default:
    return zapcore.LowercaseLevelEncoder
    }
    }
  • 时间配置

    1
    2
    3
    func (z *Zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
    encoder.AppendString(Z.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
    }
  • zapcore自带配置

    1
    2
    3
    4
    zapcore.DefaultLineEnding
    zapcore.SecondsDurationEncoder
    zapcore.FullCallerEncoder
    // 还有一堆key,随便你怎么写

有了定制器的配置后,就可以组装定制器了,这个就是再包装一下,表示这个日志形式为json/console形式

1
2
3
4
5
6
func (z *_zap) GetEncoder() zapcore.Encoder {
if Z.Encoder == "json" {
return zapcore.NewJSONEncoder(z.GetEncoderConfig())
}
return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
}
1
2
3
4
// json
{"xx":"xx","xx":"xx","xx":"xx","xx":"xx","xx":"xx"}
// console
2024/05/07 - 00:38:17.213 debug log Debug成功

ok,至此位置我们的定制器配置完成

LevelEnabler

这个东西,还记得我们之前有方法把 配置中的level string变成了level zapcore。下面我们就需要把level zapcore变成 zap.levelEnablerFunc

这个其实很好写出来,看看返回 值的类型就行了

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
func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
switch level {
case zapcore.DebugLevel:
return func(level zapcore.Level) bool { // 调试级别
return level == zap.DebugLevel
}
case zapcore.InfoLevel:
return func(level zapcore.Level) bool { // 日志级别
return level == zap.InfoLevel
}
case zapcore.WarnLevel:
return func(level zapcore.Level) bool { // 警告级别
return level == zap.WarnLevel
}
case zapcore.ErrorLevel:
return func(level zapcore.Level) bool { // 错误级别
return level == zap.ErrorLevel
}
case zapcore.DPanicLevel:
return func(level zapcore.Level) bool { // dpanic级别
return level == zap.DPanicLevel
}
case zapcore.PanicLevel:
return func(level zapcore.Level) bool { // panic级别
return level == zap.PanicLevel
}
case zapcore.FatalLevel:
return func(level zapcore.Level) bool { // 终止级别
return level == zap.FatalLevel
}
default:
return func(level zapcore.Level) bool { // 调试级别
return level == zap.DebugLevel
}
}
}

生成Core

其实到了这里我们肯定疑惑为什么要定制zap,但是我们为什么不想一下,我们之前的代码都是把各种等级的日志放在一起,那么我们能不能把这些不同等级的日志都分开来,那么我们就需要多个core,不同的core表示不同的等级,就这样

1
2
3
4
5
6
7
8
9
10
11
12
func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
//writer := FileRotatelogs.GetWriteSyncer(l.String()) // 日志分割
return zapcore.NewCore(z.GetEncoder(), os.Stdout, level)
}

func (z *_zap) GetZapCores() []zapcore.Core {
cores := make([]zapcore.Core, 0, 7)
for level := Z.TransmitLvl(); level <= zapcore.FatalLevel; level++ {
cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
}
return cores
}

Init

1
2
3
4
5
6
7
8
9
func Init() (logger *zap.Logger) {
if exists, _ := PathExists(Z.Path); !exists {
fmt.Println("create " + Z.Path + " directory")
_ = os.MkdirAll(Z.Path, os.ModePerm)
}
cores := z.GetZapCores()
logger = zap.New(zapcore.NewTee(cores...))
return logger
}

ok,至此,所有的逻辑理清楚了,是不是感觉特别简单,其实真正写起来还是有点问题的,下面看一下我的随意发挥

注意点

1
2
3
4
5
6
7
8
9
10
11
12
func (z *_zap) GetEncoderCore() zapcore.Core {
writer := GetWriteSyncer(Z.TransmitLvl().String())
return zapcore.NewCore(z.GetEncoder(), writer, z.GetLevelPriority())
}

func (z *_zap) GetZapCores() []zapcore.Core {
cores := make([]zapcore.Core, 0)
for level := Z.TransmitLvl(); level <= zapcore.FatalLevel; level++ {
cores = append(cores, z.GetEncoderCore())
}
return cores
}

这个代码是我一开始写出来的代码,这里有个很大的问题,就是测试的时候会输出很多相同的log

询问GPT:

根据您提供的代码,问题可能出在 GetZapCores 方法中。在这个方法中,您为每个日志级别创建了相应的核心,但是在创建核心时,对于每个级别,您都使用了相同的编码器和写入器。

这意味着尽管您为每个级别创建了不同的核心,但它们实际上都共享了相同的编码器和写入器。因此,无论记录哪个级别的日志,它们都会使用相同的编码器和写入器进行处理。

要解决这个问题,您需要为每个级别创建不同的编码器和写入器,而不是共享相同的实例。这样,即使使用相同的日志级别,也会使用相应级别的编码器和写入器。

由此发现,好像无论是什么level,我都是直接使用分离的函数调用

因此我们把直接的函数调用中,往函数中加入参数,保证其中的级别统一,就是上述的代码

完整代码

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
// config.go
package Z

import (
"go.uber.org/zap/zapcore"
"time"
)

var Z Zap

type Zap struct {
Prefix string `default:"gin-vue-admin" yaml:"prefix" json:"prefix"`
Level string `default:"panic" yaml:"level" json:"level"`
Path string `default:"./logs/" yaml:"path" json:"path"`
Director string `default:"director" yaml:"director" json:"director"`
EncoderLevel string `default:"LowercaseLevelEncoder" yaml:"encoder_level" json:"encoder_level"`
MaxAge int `default:"7" yaml:"max_age" json:"max_age"`
ShowLine bool `default:"true" yaml:"show_line" json:"show_line"` // 显示行
LogInConsole bool `default:"true" yaml:"log_in_console" json:"log_in_console"`
Encoder string `default:"console" yaml:"encoder" json:"encoder"`
}

func (z *Zap) GetLevelEncoder() zapcore.LevelEncoder {
switch {
case z.EncoderLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
return zapcore.LowercaseLevelEncoder
case z.EncoderLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
return zapcore.LowercaseColorLevelEncoder
case z.EncoderLevel == "CapitalLevelEncoder": // 大写编码器
return zapcore.CapitalLevelEncoder
case z.EncoderLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
return zapcore.CapitalColorLevelEncoder
default:
return zapcore.LowercaseLevelEncoder
}
}

func (z *Zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(Z.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
}

func (z *Zap) TransmitLvl() zapcore.Level {
switch z.Level {
case "debug":
return zapcore.DebugLevel
case "Info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "Dpanic":
return zapcore.DPanicLevel
case "panic":
return zapcore.PanicLevel
default:
return zapcore.DebugLevel
}
}
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
package Z

import (
"errors"
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)

var z = &_zap{}

type _zap struct{}

func Init() (logger *zap.Logger) {
if exists, _ := PathExists(Z.Path); !exists {
fmt.Println("create " + Z.Path + " directory")
_ = os.MkdirAll(Z.Path, os.ModePerm)
}
cores := z.GetZapCores()
logger = zap.New(zapcore.NewTee(cores...))
return logger
}

func PathExists(path string) (bool, error) {
fi, err := os.Stat(path)
if err == nil {
if fi.IsDir() {
return true, nil
}
return false, errors.New("存在同名文件")
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
//writer := FileRotatelogs.GetWriteSyncer(l.String()) // 日志分割
return zapcore.NewCore(z.GetEncoder(), os.Stdout, level)
}

func (z *_zap) GetZapCores() []zapcore.Core {
cores := make([]zapcore.Core, 0, 7)
for level := Z.TransmitLvl(); level <= zapcore.FatalLevel; level++ {
cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
}
return cores
}

func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
TimeKey: "ts",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding, // 行尾
EncodeLevel: Z.GetLevelEncoder(),
EncodeTime: Z.CustomTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
}
}

func (z *_zap) GetEncoder() zapcore.Encoder {
if Z.Encoder == "json" {
return zapcore.NewJSONEncoder(z.GetEncoderConfig())
}
return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
}

func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
switch level {
case zapcore.DebugLevel:
return func(level zapcore.Level) bool { // 调试级别
return level == zap.DebugLevel
}
case zapcore.InfoLevel:
return func(level zapcore.Level) bool { // 日志级别
return level == zap.InfoLevel
}
case zapcore.WarnLevel:
return func(level zapcore.Level) bool { // 警告级别
return level == zap.WarnLevel
}
case zapcore.ErrorLevel:
return func(level zapcore.Level) bool { // 错误级别
return level == zap.ErrorLevel
}
case zapcore.DPanicLevel:
return func(level zapcore.Level) bool { // dpanic级别
return level == zap.DPanicLevel
}
case zapcore.PanicLevel:
return func(level zapcore.Level) bool { // panic级别
return level == zap.PanicLevel
}
case zapcore.FatalLevel:
return func(level zapcore.Level) bool { // 终止级别
return level == zap.FatalLevel
}
default:
return func(level zapcore.Level) bool { // 调试级别
return level == zap.DebugLevel
}
}
}
1
2
3
4
5
6
7
8
9
10
11
// 简单lumberjack
func GetWriteSyncer(lvl string) zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: fmt.Sprintf("%s/%s.log", Z.Path, lvl),
MaxSize: 10,
MaxBackups: 3,
MaxAge: 28,
Compress: true,
}
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(lumberJackLogger), zapcore.AddSync(os.Stdout))
}

test

1
2
3
4
5
6
func TestInit(t *testing.T) {
l := Init()
l.Debug("log Debug成功")
l.Warn("log Warn成功")
l.Info("log Info成功")
}
1
2
3
4
5
6
7
=== RUN   TestInit
create directory
2024/05/07 - 01:19:04.204 debug log Debug成功
2024/05/07 - 01:19:04.220 warn log Warn成功
2024/05/07 - 01:19:04.220 info log Info成功
--- PASS: TestInit (0.02s)
PASS

总结

🆗,完工睡觉去了