go语言优秀的日志包zap基础自定义配置用法

zap 的官方案例中, 介绍了三种使用方式, 分别是 AdvancedConfiguration BasicConfigurationPresets 本篇文章介绍 zap 包的 BasicConfiguration 用法

上一篇文档中引用官方案例介绍了预置函数的用法, 当预置函数的logger配置不满足我们的需求时, 可以自定义配置logger

自定义配置logger, 有如下选项可以配置

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
type Config struct {
// Level is the minimum enabled logging level. Note that this is a dynamic
// level, so calling Config.Level.SetLevel will atomically change the log
// level of all loggers descended from this config.
Level AtomicLevel `json:"level" yaml:"level"`
// Development puts the logger in development mode, which changes the
// behavior of DPanicLevel and takes stacktraces more liberally.
Development bool `json:"development" yaml:"development"`
// DisableCaller stops annotating logs with the calling function's file
// name and line number. By default, all logs are annotated.
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// DisableStacktrace completely disables automatic stacktrace capturing. By
// default, stacktraces are captured for WarnLevel and above logs in
// development and ErrorLevel and above in production.
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// Encoding sets the logger's encoding. Valid values are "json" and
// "console", as well as any third-party encodings registered via
// RegisterEncoder.
Encoding string `json:"encoding" yaml:"encoding"`
// EncoderConfig sets options for the chosen encoder. See
// zapcore.EncoderConfig for details.
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// OutputPaths is a list of URLs or file paths to write logging output to.
// See Open for details.
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// ErrorOutputPaths is a list of URLs to write internal logger errors to.
// The default is standard error.
//
// Note that this setting only affects internal errors; for sample code that
// sends error-level logs to a different location from info- and debug-level
// logs, see the package-level AdvancedConfiguration example.
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// InitialFields is a collection of fields to add to the root logger.
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

Level AtomicLevel

zap 提供如下日志级别

  • debug
  • info
  • warn
  • error
  • dpanic 如果开发模式开启 DPanicLevel 日志会产生 panic, 反之如果开发模式没有开启, 则仅会打印一行日志, 不会有 panic 抛出
  • panic 在日志记录后产生 panic
  • fatal 在日志记录后调用 os.Exit(1)
1
2
3
4
// 生成 debug level 对象
logLevel := zap.NewAtomicLevelAt(zapcore.DebugLevel)
// 生成 error level 对象
errLevel := zap.NewAtomicLevelAt(zapcore.ErrorLevel)

Development

开发模式开关, 如果该字段的值为true, 意为开启开发模式. 在开发模式下, DPanicLevel 的错误就打印出完整的堆栈, 并随后抛出一个 panic, 如果你不希望因为日志中的 panic 影响程序继续运行, 那么你必须处理这个 panic.

1
2
3
4
// 预置函数 zap.NewDevelopment() 中开发模式默认为开启
logger, err := zap.NewDevelopment()
...
logger.DPanic("diy dpanic")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2020-03-17T23:27:30.380+0800    DPANIC  log/l2.go:26    diy dpanic
main.main
/Users/lvrui/go/src/awesomeProject/log/l2.go:26
runtime.main
/usr/local/go/src/runtime/proc.go:203
panic: diy dpanic

goroutine 1 [running]:
go.uber.org/zap/zapcore.(*CheckedEntry).Write(0xc0000c62c0, 0x0, 0x0, 0x0)
/Users/lvrui/go/src/go.uber.org/zap/zapcore/entry.go:230 +0x546
go.uber.org/zap.(*SugaredLogger).log(0xc0000fbd78, 0xc0000b2203, 0x0, 0x0, 0xc0000fbd90, 0x1, 0x1, 0x0, 0x0, 0x0)
/Users/lvrui/go/src/go.uber.org/zap/sugar.go:234 +0x100
go.uber.org/zap.(*SugaredLogger).DPanic(...)
/Users/lvrui/go/src/go.uber.org/zap/sugar.go:118
main.main()
/Users/lvrui/go/src/awesomeProject/log/l2.go:26 +0x30c
exit status 2

如果 Development 字段的值为 false, 意为关闭开发模式, 此时 DPanicLevel 的错误不会打印完整的堆栈, 也不会抛出 panic, 仅会记录日志

1
2
3
4
// 预置函数 zap.NewProduction() 中开发模式默认为关闭
logger, err := zap.NewProduction()
...
logger.DPanic("diy dpanic")
1
{"level":"dpanic","ts":1584459058.8611479,"caller":"log/l2.go:26","msg":"diy dpanic","stacktrace":"main.main\n\t/Users/lvrui/go/src/awesomeProject/log/l2.go:26\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:203"}

DisableCaller

是否禁用调用信息. 该字段值为 true 时, 日志中将不再显示该日志所在的函数调用信息

1
2
3
// false 显示调用信息
{"level":"info","time":"2020-03-17T23:33:57.881+0800","caller":"log/l1.go:44","message":"logger construction succeeded","app":"zapdex","key":"value","number":9}
{"level":"info","time":"2020-03-17T23:33:57.881+0800","caller":"log/l1.go:51","message":"logger construction succeeded","app":"zapdex","key":"value","number":9}
1
2
3
// true 关闭调用信息
{"level":"info","time":"2020-03-17T23:34:25.064+0800","message":"logger construction succeeded","app":"zapdex","key":"value","number":9}
{"level":"info","time":"2020-03-17T23:34:25.064+0800","message":"logger construction succeeded","app":"zapdex","key":"value","number":9}

DisableStacktrace

是否禁用自动堆栈跟踪捕获.

  • 默认情况下, 在 warn level 及以上日志捕获 stacktrace
  • 在生产环境, 一般在 error level 及以上日志捕获 stacktrace

Sampling

流控配置, 也叫采样. 单位是每秒钟, 作用是限制日志在每秒钟内的输出数量, 以防止CPU和IO被过度占用

源码

1
2
3
4
type SamplingConfig struct {
Initial int `json:"initial" yaml:"initial"`
Thereafter int `json:"thereafter" yaml:"thereafter"`
}

有两个参数可以配置

  • Initial
  • Thereafter

在一秒钟内, 如果某个级别的日志输出量超过了 Initial, 那么在超过之后, 每 Thereafter 条日志才会输出一条, 其余的日志都将被删除

在官方文档中有这么一句话:

1
2
3
// Keep in mind that zap's sampling implementation is optimized for speed over
// absolute precision; under load, each tick may be slightly over- or
// under-sampled.

由此可见 zap 性能至上的设计理念

Encoding

指定日志编码器, 目前仅支持两种编码器

  • console
  • json

上篇 zap 预置函数用法中的 NewProduction 用的就是 json, 而 NewDevelopment 用的则是 console

在实际生产环境中, 一般推荐使用 json 编码器, 方便日志收集器收集, 免去格式化成结构化日志的CPU性能损耗

EncoderConfig

EncoderConfig 接收一个 zapcore.EncoderConfig 类型的变量, 支持如下配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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"`
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"`
}

Key

MessageKey LevelKey TimeKey NameKey CallerKey StacktraceKey 用来标识日志中对应字段的Key的名字

LineEnding

设置每行日志的结束符, 默认使用换行符

EncodeLevel

配置日志级别编码器, 可以配置例如带控制台颜色的输出, 全小写输出或首字母大写输出等

EncodeTime

配置时间编码器, 配置显示时间的格式

EncodeDuration

配置序列化时间的类型, 可配置int64类型的毫秒, int64类型的纳秒, float64类型的秒, 和字符串类型的时间

EncodeCaller

配置调用点编码器, 可配置全路径或短路径的显示形式

EncodeName

配置logger名称编码器

OutputPaths

配置日志标准输出, 接收数据类型为 []string 意味着可以配置多个路径作为日志的输出路径, 一般情况可以仅配置标准输出或输出到文件, 如有需求的话, 也可以两者同时配置.

也可以自定义输出协议, 但是需要使用RegisterSink方法先注册一个该协议对应的工厂方法, 该工厂方法实现了Sink接口

ErrorOutputPaths

配置标准错误输出, 使用方式同OutputPaths

InitialFields

初始化字段配置, 该配置的字段会以结构化的形式打印在每条日志输出中

配置完整示例

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
package main

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

func main() {

logLevel := zap.NewAtomicLevelAt(zapcore.DebugLevel)

var zc = zap.Config{
Level: logLevel,
Development: false,
DisableCaller: false,
DisableStacktrace: false,
Sampling: nil,
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
TimeKey: "time",
NameKey: "name",
CallerKey: "caller",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeName: zapcore.FullNameEncoder,
},
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
InitialFields: map[string]interface{}{"app": "zapdex"},
}

logger, err := zc.Build()
if err != nil {
panic(err)
}
defer logger.Sync()

logger.Info("logger construction succeeded",
zap.String("key", "value"),
zap.Int("number", 9),
)

sugarLogger := logger.Sugar()

sugarLogger.Infow("logger construction succeeded",
"key", "value", "number", 9,
)