脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - GO语言框架快速集成日志模块的操作方法

GO语言框架快速集成日志模块的操作方法

2022-07-13 13:17Masters Golang

zap是一个可以在go项目中进行快速, 结构化且分级的日志记录包, git star数高达16.3k, Git 项目地址, 在各大公司项目中被广泛使用,这篇文章主要介绍了GO语言框架中如何快速集成日志模块,需要的朋友可以参考下

前言

在我们的日常开发中, 日志模块永远是最基础且最重要的一个模块, 它可以有效的帮我们发现问题, 定位问题, 最后去解决问题;

zap包的集成

简介

zap是一个可以在go项目中进行快速, 结构化且分级的日志记录包, git star数高达16.3k, Git 项目地址, 在各大公司项目中被广泛使用;

最基础的使用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
    "go.uber.org/zap"
    "time"
)
func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    logger.Info(" log info msg",
        zap.String("name", "掘金"),
        zap.Int("num", 3),
        zap.Duration("timer", time.Minute),
    )
}
?
1
2
{"level":"info","ts":1657600159.826612,"caller":"log/main.go:11","msg":" log info msg","name":"脚本","num":3,"timer":60}
{"level":"warn","ts":1657600159.8266969,"caller":"log/main.go:16","msg":" this is err msg","msg":"code err"}

可以看到上面就是打印出来的log, 当然这是用的默认配置, 所以格式和输出可能不太符合我们的要求, 我们可以自己修改配置来完成定制化log;

定制化

?
1
2
3
4
5
6
7
// NewProduction builds a sensible production Logger that writes InfoLevel and
// above logs to standard error as JSON.
//
// It's a shortcut for NewProductionConfig().Build(...Option).
func NewProduction(options ...Option) (*Logger, error) {
   return NewProductionConfig().Build(options...)
}

可以看到生成log的方法其实就是用 configbuild 来构造一个记录器, 我们试试自定义一下;

?
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
package main
import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "time"
)
func main() {
    conf := zap.NewProductionConfig()
 
    // 可以把输出方式改为控制台编码, 更容易阅读
    conf.Encoding = "console"
    // 时间格式自定义
    conf.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
        enc.AppendString("[" + t.Format("2006-01-02 15:04:05") + "]")
    }
    // 打印路径自定义
    conf.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
        encoder.AppendString("[" + caller.TrimmedPath() + "]")
    }
    // 级别显示自定义
    conf.EncoderConfig.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
        encoder.AppendString("[" + level.String() + "]")
    }
 
    logger, _ := conf.Build()
    logger.Info("service start")
 
    logger.Info("info msg",
        zap.String("name", "掘金"),
        zap.Int("num", 3),
        zap.Duration("timer", time.Minute),
    )
}
?
1
2
[2022-07-12 14:57:18]   [info]  [log/main.go:28]        service start
[2022-07-12 14:57:18]   [info]  [log/main.go:30]        info msg        {"name": "掘金", "num": 3, "timer": 60}

这种日志一般大家看起来就比较舒服了, 特别是打印json结构的话可以直接复制解析器里面去看, 没有json编码的各种转义;

zap包还是很灵活的, 基本上配置参数都支持自定义(输出键名, 格式等等), 大家可以按需配置使用;

?
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
// An EncoderConfig allows users to configure the concrete encoders supplied by
// zapcore.
type EncoderConfig struct {
    // Set the keys used for each log entry. If any key is empty, that portion
    // of the entry is omitted.
    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"`
    LineEnding    string `json:"lineEnding" yaml:"lineEnding"`
    // Configure the primitive representations of common complex types. For
    // example, some users may want all time.Times serialized as floating-point
    // seconds since epoch, while others may prefer ISO8601 strings.
    EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
    EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
    EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
    EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
    // Unlike the other primitive type encoders, EncodeName is optional. The
    // zero value falls back to FullNameEncoder.
    EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
    // Configures the field separator used by the console encoder. Defaults
    // to tab.
    ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}

进阶封装

项目里面生产环境为了方便收集日志可能都比较喜欢用json, 开发环境中大家又都比较喜欢console方便调试, 还有比如希望接口/服务日志带上属于自己的唯一请求标识这种, 以及对日志进行分类保存等等, 就需要对zap包进行再一次的封装;

下面是我自己封装的log包代码, 可以很方便的解决上面的问题, 大家可以当做参考;

index.go 记录器初始化, 根据配置选择编码输出方式及日志文件保存逻辑, 以及一些自己自定义的输出标准;

?
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package logging
 
import (
    "go.uber.org/zap"
    "go.uber.org/zap/buffer"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
    "os"
    "path"
    "strings"
)
 
type (
    Conf struct {
        Path    string // 日志路径
        Encoder string // 编码器选择
    }
    logItem struct {
        FileName string
        Level    zap.LevelEnablerFunc
    }
    Encoder interface {
        Config() zapcore.Encoder
        WithKey(key string) Encoder
        WithField(key, val string) Encoder
        Debug(msg string)
        Debugf(format string, v ...interface{})
        Info(msg string)
        Infof(format string, v ...interface{})
        Warn(msg string)
        Warnf(format string, v ...interface{})
        Error(msg string)
        Errorf(format string, v ...interface{})
        Fatal(msg string)
        Fatalf(format string, v ...interface{})
    }
)
 
var (
    maxSize    = 200 // 每个日志文件最大尺寸200M
    maxBackups = 20  // 日志文件最多保存20个备份
    maxAge     = 30  // 保留最大天数
    _logger    *zap.Logger
    _pool      = buffer.NewPool()
    c          Conf
 
    ConsoleEncoder = "console" // 控制台输出
    JsonEncoder    = "json"    // json输出
)
 
// Init 初始化日志.
func Init(conf Conf) {
    c = conf
    prefix, suffix := getFileSuffixPrefix(c.Path)
 
    infoPath := path.Join(prefix + ".info" + suffix)
    errPath := path.Join(prefix + ".err" + suffix)
    items := []logItem{
        {
            FileName: infoPath,
            Level: func(level zapcore.Level) bool {
                return level <= zap.InfoLevel
            },
        },
        {
            FileName: errPath,
            Level: func(level zapcore.Level) bool {
                return level > zap.InfoLevel
            },
        },
    }
 
    NewLogger(items)
}
 
// NewLogger 日志.
func NewLogger(items []logItem) {
    var (
        cfg   zapcore.Encoder
        cores []zapcore.Core
    )
    switch c.Encoder {
    case JsonEncoder:
        cfg = NewJsonLog().Config()
    case ConsoleEncoder:
        cfg = NewConsoleLog().Config()
    default:
        cfg = NewConsoleLog().Config()
    }
 
    for _, v := range items {
        hook := lumberjack.Logger{
            Filename:   v.FileName,
            MaxSize:    maxSize,    // 每个日志文件保存的最大尺寸 单位:M
            MaxBackups: maxBackups, // 日志文件最多保存多少个备份
            MaxAge:     maxAge,     // 文件最多保存多少天
            Compress:   true,       // 是否压缩
            LocalTime:  true,       // 备份文件名本地/UTC时间
        }
        core := zapcore.NewCore(
            cfg, // 编码器配置;
            zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制台和文件
            v.Level, // 日志级别
        )
        cores = append(cores, core)
    }
 
    // 开启开发模式,堆栈跟踪
    caller := zap.AddCaller()
    // 开发模式
    development := zap.Development()
    // 二次封装
    skip := zap.AddCallerSkip(1)
    // 构造日志
    _logger = zap.New(zapcore.NewTee(cores...), caller, development, skip)
    return
}
 
// GetEncoder 获取自定义编码器.
func GetEncoder() Encoder {
    switch c.Encoder {
    case JsonEncoder:
        return NewJsonLog()
    case ConsoleEncoder:
        return NewConsoleLog()
    default:
        return NewConsoleLog()
    }
}
 
// GetLogger 获取日志记录器.
func GetLogger() *zap.Logger {
    return _logger
}
 
// getFileSuffixPrefix 文件路径切割
func getFileSuffixPrefix(fileName string) (prefix, suffix string) {
    paths, _ := path.Split(fileName)
    base := path.Base(fileName)
    suffix = path.Ext(fileName)
    prefix = strings.TrimSuffix(base, suffix)
    prefix = path.Join(paths, prefix)
    return
}
 
// getFilePath 自定义获取文件路径.
func getFilePath(ec zapcore.EntryCaller) string {
    if !ec.Defined {
        return "undefined"
    }
    buf := _pool.Get()
    buf.AppendString(ec.Function)
    buf.AppendByte(':')
    buf.AppendInt(int64(ec.Line))
    caller := buf.String()
    buf.Free()
    return caller
}

console.go 控制台编码输出, 支持自定义;

?
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
package logging
 
import (
    "fmt"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "time"
)
 
type ConsoleLog struct {
    val string
}
 
func NewConsoleLog() Encoder {
    return new(ConsoleLog)
}
 
// Config 自定义配置.
func (slf *ConsoleLog) Config() zapcore.Encoder {
    var (
        cfg = zap.NewProductionEncoderConfig()
    )
 
    // 时间格式自定义
    cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
        enc.AppendString("[" + t.Format("2006-01-02 15:04:05") + "]")
    }
    // 打印路径自定义
    cfg.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
        encoder.AppendString("[" + getFilePath(caller) + "]")
    }
    // 级别显示自定义
    cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
        encoder.AppendString("[" + level.String() + "]")
    }
    return zapcore.NewConsoleEncoder(cfg)
}
 
// WithKey 添加单个键.
func (slf *ConsoleLog) WithKey(key string) Encoder {
    slf.val = slf.val + "[" + key + "]    "
    return slf
}
 
// WithField 添加字段.
func (slf *ConsoleLog) WithField(key, val string) Encoder {
    slf.val = slf.val + fmt.Sprintf("[%s:%s]    ", key, val)
    return slf
}
 
func (slf *ConsoleLog) Debug(msg string) {
    _logger.Debug(slf.val + msg)
}
 
func (slf *ConsoleLog) Debugf(format string, v ...interface{}) {
    _logger.Debug(fmt.Sprintf(slf.val+format, v...))
}
 
func (slf *ConsoleLog) Info(msg string) {
    _logger.Info(slf.val + msg)
}
 
func (slf *ConsoleLog) Infof(format string, v ...interface{}) {
    _logger.Info(fmt.Sprintf(slf.val+format, v...))
}
 
func (slf *ConsoleLog) Warn(msg string) {
    _logger.Warn(slf.val + msg)
}
 
func (slf *ConsoleLog) Warnf(format string, v ...interface{}) {
    _logger.Warn(fmt.Sprintf(slf.val+format, v...))
}
 
func (slf *ConsoleLog) Error(msg string) {
    _logger.Error(slf.val + msg)
}
 
func (slf *ConsoleLog) Errorf(format string, v ...interface{}) {
    _logger.Error(fmt.Sprintf(slf.val+format, v...))
}
 
func (slf *ConsoleLog) Fatal(msg string) {
    _logger.Fatal(slf.val + msg)
}
 
func (slf *ConsoleLog) Fatalf(format string, v ...interface{}) {
    _logger.Fatal(fmt.Sprintf(slf.val+format, v...))
}

json.go json编码输出, 支持自定义;

?
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
package logging
 
import (
    "fmt"
    "go.uber.org/zap/zapcore"
    "time"
 
    "go.uber.org/zap"
)
 
type JsonLog struct {
    fields []zap.Field
    val    string
}
 
// NewJsonLog  自定义添加log field.
func NewJsonLog() Encoder {
    return &JsonLog{fields: make([]zap.Field, 0)}
}
 
// Config 自定义配置.
func (slf *JsonLog) Config() zapcore.Encoder {
    var (
        cfg = zap.NewProductionEncoderConfig()
    )
 
    // 时间格式自定义
    cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
        enc.AppendString(t.Format("2006-01-02 15:04:05"))
    }
    // 打印路径自定义
    cfg.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
        encoder.AppendString(getFilePath(caller))
    }
    // 级别显示自定义
    cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
        encoder.AppendString(level.String())
    }
    return zapcore.NewJSONEncoder(cfg)
}
 
// WithKey 添加单个键.
func (slf *JsonLog) WithKey(key string) Encoder {
    slf.val = slf.val + key + " "
    return slf
}
 
// WithField 添加字段.
func (slf *JsonLog) WithField(key, val string) Encoder {
    slf.fields = append(slf.fields, zap.String(key, val))
    return slf
}
 
func (slf *JsonLog) Debug(msg string) {
    _logger.Debug(slf.val+msg, slf.fields...)
}
 
func (slf *JsonLog) Debugf(format string, v ...interface{}) {
    _logger.Debug(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
 
func (slf *JsonLog) Info(msg string) {
    _logger.Info(slf.val+msg, slf.fields...)
}
 
func (slf *JsonLog) Infof(format string, v ...interface{}) {
    _logger.Info(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
 
func (slf *JsonLog) Warn(msg string) {
    _logger.Warn(slf.val+msg, slf.fields...)
}
 
func (slf *JsonLog) Warnf(format string, v ...interface{}) {
    _logger.Warn(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
 
func (slf *JsonLog) Error(msg string) {
    _logger.Error(slf.val+msg, slf.fields...)
}
 
func (slf *JsonLog) Errorf(format string, v ...interface{}) {
    _logger.Error(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
 
func (slf *JsonLog) Fatal(msg string) {
    _logger.Fatal(slf.val+msg, slf.fields...)
}
 
func (slf *JsonLog) Fatalf(format string, v ...interface{}) {
    _logger.Fatal(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}

service.go 标准输出方法, 方便直接调用;

?
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
package logging
 
import (
    "fmt"
)
 
func Sync() {
    _ = _logger.Sync()
}
 
func Debug(msg string) {
    _logger.Debug(msg)
}
 
func Debugf(format string, v ...interface{}) {
    _logger.Debug(fmt.Sprintf(format, v...))
}
 
func Info(msg string) {
    _logger.Info(msg)
}
 
func Infof(format string, v ...interface{}) {
    _logger.Info(fmt.Sprintf(format, v...))
}
 
func Warn(msg string) {
    _logger.Warn(msg)
}
 
func Warnf(format string, v ...interface{}) {
    _logger.Warn(fmt.Sprintf(format, v...))
}
 
func Error(msg string) {
    _logger.Error(msg)
}
 
func Errorf(format string, v ...interface{}) {
    _logger.Error(fmt.Sprintf(format, v...))
}
 
func Fatal(msg string) {
    _logger.Fatal(msg)
}
 
func Fatalf(format string, v ...interface{}) {
    _logger.Fatal(fmt.Sprintf(format, v...))
}

上面的进阶代码摘自我开发的一个git项目中, 主要是一个Go的标准项目布局, 封装了一些常用的组件, 有兴趣的朋友可以了解一下, 对新手还是很友好的;

到此这篇关于GO语言框架快速集成日志模块的操作方法的文章就介绍到这了,更多相关go语言日志模块内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7119390985863299085

延伸 · 阅读

精彩推荐
  • Golang关于Golang中range指针数据的坑详解

    关于Golang中range指针数据的坑详解

    这篇文章主要给大家介绍了关于Golang中range指针数据的坑的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值...

    脚本之家3282020-05-22
  • Golang详解Gotorch多机定时任务管理系统

    详解Gotorch多机定时任务管理系统

    遵循着“学一门语言最好的方式是使用它”的理念,想着用Go来实现些什么,刚好有一个比较让我烦恼的问题,于是用Go解决一下,即使不在生产环境使用,...

    枕边书7492021-08-06
  • GolangGo语言快速入门图文教程

    Go语言快速入门图文教程

    Go是 Goolge 开发的一种静态型、编译型、并发型,并具有垃圾回收功能的语言,Go 语言上手非常容易,它的风格类似于 C 语言,Go 语言号称是互联网时代的...

    异常编程3992021-06-26
  • Golanggolang修改结构体中的切片值方法

    golang修改结构体中的切片值方法

    这篇文章主要介绍了golang修改结构体中的切片值方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    晨梦~思雨6452021-03-26
  • Golanggolang如何实现mapreduce单进程版本详解

    golang如何实现mapreduce单进程版本详解

    这篇文章主要给大家介绍了关于golang如何实现mapreduce单进程版本的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学...

    VINLLEN CHEN4822020-05-13
  • Golanggoland Duration 和time的区别说明

    goland Duration 和time的区别说明

    这篇文章主要介绍了goland Duration 和time的区别说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    it_manman8262021-03-12
  • Golanggolang 如何实现HTTP代理和反向代理

    golang 如何实现HTTP代理和反向代理

    这篇文章主要介绍了golang 实现HTTP代理和反向代理的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    猫哭10872021-06-22
  • Golang在 Golang 中使用 Cobra 创建 CLI 应用

    在 Golang 中使用 Cobra 创建 CLI 应用

    这篇文章主要介绍了在 Golang 中使用 Cobra 创建 CLI 应用,来看下 Cobra 的使用,这里我们使用的 go1.13.3 版本,使用 Go Modules 来进行包管理,需要的朋友可以参考...

    吱吱吱 (piperck) XD4962022-01-26