zap日志框架
# 一、概述
zap (opens new window)是uber开源的一个go语言日志框架,根据官方介绍性能比类似的结构化日志包更好——也比标准库更快。
# 二、安装
go get -u go.uber.org/zap
# 三、Zap框架学习
# 3.1 创建实例
zap提供两种类型的日志记录器SugaredLogger
和Logger
,接下来,我们针对这两种日志记录器,进行演示说明。
注意
默认情况下日志都会打印到控制台界面
# 3.1.1 SugaredLogger
主要用于性能很好但不是很关键的上下文中,他比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录
package main
import (
"fmt"
"log"
"net/http"
"os"
"go.uber.org/zap"
)
func zapHttpGet(url string) {
log := zapSugaredLogger()
// Debug日志格式
log.Debugf("开始请求url: %s", url)
resp, err := http.Get(url)
if err != nil {
log.Errorf("url请求失败:%s", err.Error())
} else {
log.Infof("HTTP Successfully")
resp.Body.Close()
}
}
func zapSugaredLogger() *zap.SugaredLogger {
// 通过Logger实例化sugaredLogger对象
logger, err := zap.NewDevelopment()
sugaredLogger := logger.Sugar()
if err != nil {
fmt.Printf("创建zap logger 失败 %s\n", err.Error())
return nil
}
return sugaredLogger
}
func main() {
SetupLogger()
zapHttpGet("https://www.baidu.com")
zapHttpGet("https://www.g.com")
}
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
打印结果
2023-03-29T15:27:12.788+0800 DEBUG base/main.go:18 开始请求url: https://www.baidu.com
2023-03-29T15:27:12.850+0800 INFO base/main.go:23 HTTP Successfully
2023-03-29T15:27:12.851+0800 DEBUG base/main.go:18 开始请求url: https://www.g.com
2023-03-29T15:27:12.856+0800 ERROR base/main.go:21 地址请求失败:Get "https://www.g.com": dial tcp: lookup www.g.com: no such host
main.zapHttpGet
D:/工作/goapps/base/main.go:21
main.main
D:/工作/goapps/base/main.go:51
runtime.main
C:/Program Files/Go/src/runtime/proc.go:250
2
3
4
5
6
7
8
9
10
# 3.1.2 Logger
在每一微秒和每一次内存分配都很重要的上下文中,使用
Logger
。它甚至比SugaredLogger
更快,内存分配次数也更少,但它只支持强类型的结构化日志记录
Logger 有三种方式创建,不同的方法只是打印的日志信息不一样,我们一一举例说明
NewProduction()
package main
import (
"fmt"
"go.uber.org/zap"
"net/http"
)
func zapHttpGet(url string) {
log := zapLogger()
resp, err := http.Get(url)
if err != nil {
log.Error("url请求失败:%s", zap.String("url", url), zap.Error(err))
} else {
log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
resp.Body.Close()
}
}
func zapLogger() *zap.Logger {
logger, err := zap.NewProduction()
if err != nil {
fmt.Printf("创建zap logger 失败 %s\n", err.Error())
return nil
}
return logger
}
func main() {
SetupLogger()
zapHttpGet("https://www.baidu.com")
zapHttpGet("https://www.g.com")
}
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
打印结果
{"level":"info","ts":1680075750.81108,"caller":"base/main.go:22","msg":"请求成功","url":"https://www.baidu.com","Status":"200 OK"}
{"level":"error","ts":1680075750.815755,"caller":"base/main.go:20","msg":"url请求失败:%s","url":"https://www.g.com","error":"Get \"https://www.g.com\": dial tcp: up www.g.com: no such host","stacktrace":"main.zapHttpGet\n\tD:/工作/goapps/base/main.go:20\nmain.main\n\tD:/工作/goapps/base/main.go:50\nruntime.main\n\tC:/Progriles/Go/src/runtime/proc.go:250"}
2
NewDevelopment()
package main
import (
"fmt"
"go.uber.org/zap"
"net/http"
)
func zapHttpGet(url string) {
log := zapLogger()
resp, err := http.Get(url)
if err != nil {
log.Error("url请求失败:%s", zap.String("url", url), zap.Error(err))
} else {
log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
resp.Body.Close()
}
}
func zapLogger() *zap.Logger {
logger, err := zap.NewDevelopment()
if err != nil {
fmt.Printf("创建zap logger 失败 %s\n", err.Error())
return nil
}
return logger
}
func main() {
zapHttpGet("https://www.baidu.com")
zapHttpGet("https://www.g.com")
}
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
打印结果
2023-03-29T15:44:54.855+0800 INFO base/main.go:15 请求成功 {"url": "https://www.baidu.com", "Status": "200 OK"}
2023-03-29T15:44:54.865+0800 ERROR base/main.go:13 url请求失败:%s {"url": "https://www.g.com", "error": "Get \"https://www.g.com\": dial tcp: lookup www.g.com: no such host"}
main.zapHttpGet
D:/工作/goapps/base/main.go:13
main.main
D:/工作/goapps/base/main.go:42
runtime.main
C:/Program Files/Go/src/runtime/proc.go:250
2
3
4
5
6
7
8
9
NewExample()
package main
import (
"go.uber.org/zap"
"net/http"
)
func zapHttpGet(url string) {
log := zapLogger()
resp, err := http.Get(url)
if err != nil {
log.Error("url请求失败:%s", zap.String("url", url), zap.Error(err))
} else {
log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
resp.Body.Close()
}
}
func zapLogger() *zap.Logger {
logger := zap.NewExample()
return logger
}
func main() {
zapHttpGet("https://www.baidu.com")
zapHttpGet("https://www.g.com")
}
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
打印结果
{"level":"info","msg":"请求成功","url":"https://www.baidu.com","Status":"200 OK"}
{"level":"error","msg":"url请求失败:%s","url":"https://www.g.com","error":"Get \"https://www.g.com\": dial tcp: lookup www.g.com: no such host"}
2
# 3.1.3 总结
总结
- SugaredLogger 不但可以基于Logger的.Sugar()创建,还可以直接定义一个全局SugaredLogger直接调用
- Logger 的三种模式,
NewExample
和NewProduction
使用的是json格式
输出,NewDevelopment
使用行的形式
输出。
# 3.2 自定义日志格式
上面介绍的几种日志是zap框架帮我们定义好的配置,我们只是实例化后就可以使用,日志也是直接输出到控制台,如果我们想自定义日志格式,并且想把日志写入到日志文件,就需要我们使用自定义的方式,定义Logger。(当然,这也是生产中常用到的模式)
# 3.2.1 zap.New()
zap框架中,使用zap.New()
方法手动传递自定义日志格式配置。
- 源码预览
参数zapcore.Core,是Core的一个接口类型,通过查看源码,可以了解到有一个ioCore的实例实现了
Core
提供的几个接口,这里我就不去探究源码,因为水平也有限,比较核心的就是Encoder
、WriteSyncer
、LogLevel
三个核心参数。
func New(core zapcore.Core, options ...Option) *Logger {
if core == nil {
return NewNop()
}
log := &Logger{
core: core,
errorOutput: zapcore.Lock(os.Stderr),
addStack: zapcore.FatalLevel + 1,
clock: zapcore.DefaultClock,
}
return log.WithOptions(options...)
}
2
3
4
5
6
7
8
9
10
11
12
# 3.2.2 核心参数Encoder
Encoder编码器(如何写入日志)。这里我们可以直接使用zap包中的
NewJSONEncoder()
,并使用预先设置的ProductionEncoderConfig()
。
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
// 这样是直接使用zap库默认配置
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
// 源码NewProductionEncoderConfig函数体 主要控制以下参数 因此我们可以按需修改这些配置
func NewProductionEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
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
示例
// 自定义函数 可以修改zap库中默认的配置,后面调用时,就是我们自定义后的配置
func getEncoder() zapcore.Encoder {
//自定义编码配置
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //修改时间编码器
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别
return zapcore.NewJSONEncoder(encoderConfig)
}
2
3
4
5
6
7
8
9
10
# 3.2.3 核心参数WriteSyncer
控制日志输出到哪里
// 指定写入的文件
file, _ := os.Create("./app.log")
// 把os.Create的类型转换为所需要的WriteSyncer类型
writeSyncer := zapcore.AddSync(file)
2
3
4
示例
func getWriterSyncer() zapcore.WriteSyncer {
file, _ := os.Create("./app.log")
// 如果想要同时输入到文件和控制台 则需要使用io.MultiWriter
//ws := io.MultiWriter(file,os.Stdout)
return zapcore.AddSync(file)
}
2
3
4
5
6
# 3.2.4 核心参数LevelEnabler
设置日志级别,日志级别zap库中直接定义好多种级别类型,直接通过zapcore.***Level调用即可
# 3.2.5 完整示例
通过上面三个参数的介绍,我们自定义一个完整的demo示例
package main
import (
"net/http"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func zapHttpGet(url string) {
// 实例化 logger对象
log := initLogger()
resp, err := http.Get(url)
if err != nil {
log.Error("url请求失败:", zap.String("url", url), zap.Error(err))
} else {
log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
resp.Body.Close()
}
}
func main() {
zapHttpGet("https://www.baidu.com")
zapHttpGet("https://www.g.com")
}
// 初始化 Logger 对象
func initLogger() *zap.Logger {
encoder := getEncoder()
writerSyncer := getLogWriter()
// 第一种方式: 同时写入文件可控制台 使用以下配置
// core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zapcore.DebugLevel)
core := zapcore.NewCore(encoder, writerSyncer, zapcore.DebugLevel)
// AddCaller 日志显示函数调用
logger := zap.New(core, zap.AddCaller())
// 如果使用SugaredLogger 则可以打开,也可以在调用initLogger函数的时候,再次调用.Sugar()
//SugaredLogger = logger.Sugar()
return logger
}
// getEncoder 使用zap库默认字段
// NewJSONEncoder json格式 NewConsoleEncoder文本格式
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
// 时间格式化
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 日志级别转大写
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
//return zapcore.NewJSONEncoder(zap.encoderConfig())
}
// getLogWriter 定义日志文件
func getLogWriter() zapcore.WriteSyncer {
file, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
// 第二种方式: 同时写入文件可控制台 则需要使用io.MultiWriter
//ws := io.MultiWriter(file,os.Stdout)
return zapcore.AddSync(file)
}
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
日志文件内容
2023-03-29T17:10:31.783+0800 INFO base/main.go:22 请求成功 {"url": "https://www.baidu.com", "Status": "200 OK"}
2023-03-29T17:10:31.796+0800 ERROR base/main.go:23 url请求失败: {"url": "https://www.g.com", "error": "Get \"https://www.g.com\": dial tcp: lookup www.g.com: no such host"}
2
3
# 四、日志切割
日志切割这里使用另一个库
Lumberjack
,因为zap库默认不支持日志切割
# 4.1 安装
gopkg.in/natefinch/lumberjack.v2
# 4.2 集成至zap
直接修改getWriterSyncer
// getLogWriter 定义日志文件
func getLogWriter() zapcore.WriteSyncer {
//file, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
// 如果想要同时输入到文件和控制台 则需要使用io.MultiWriter
//ws := io.MultiWriter(file,os.Stdout)
lumberWriteSyncer := &lumberjack.Logger{
Filename: "./app.log",
MaxSize: 50,
MaxAge: 7,
MaxBackups: 10,
LocalTime: false,
Compress: false,
}
return zapcore.AddSync(lumberWriteSyncer)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15