如果你想学到更多实用知识。
可以关注我的公众号:【前端驿站Lite】,一个不止分享前端的地方 ᕦ( •̀∀•́)ᕤ
介绍
zap是一款go开源的日志库。
特点
- 支持七种日志级别:
Debug
、Info
、Warn
、Error
、DPanic
、Panic
、Fatal
,其中DPanic
是指在开发环境下(development
)记录日志后会进行panic
。 - 能打印最基本的信息,例如调用的文件、函数名称、错误代码行号、记录时间等
- 能够持久化存储日志,例如将日志记录在文件中,并且能够根据需要来切割日志
安装
shell
go get -u go.uber.org/zap
创建实例
使用zap
前,需要做一些配置,最终就是为了得到日志记录器,然后调用该记录器的方法来记录日志。
zap
提供了两种类型的日志记录器SugaredLogger
和Logger
。
Logger
比SugaredLogger
性能更高,因为Logger
要直接指定好数据类型没有反射,所以性能更高。但不如SugaredLogger
灵活 ,SugaredLogger
可以直接传入任意类型的数据。
除了上面两种开箱即用的日志记录器,zap还支持使用zap.New(...)
自定义日志记录器。
注: 默认情况下日志都会打印到应用程序的终端界面
Logger
可以通过zap.NewProduction()
、zap.NewDevelopment()
或者 zap.Example()(不常用,不做过多介绍)
来创建一个Logger
。
这三个方法的区别在于他将记录的信息不同,输出格式不同。
注意:
- 输出日志传入的参数只能是
string
类型 zap.NewProduction()
它只能记录InfoLevel
及以上的日志级别
go
var log *zap.Logger //定义一个全局的Logger
log = zap.NewExample()
log, _ = zap.NewDevelopment()
log, _ = zap.NewProduction()
log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")
log.Fatal("This ia a fatal message")
//zap.NewExample() 输出
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}
//zap.NewDevelopment() 输出
2022-11-19T10:07:36.345+0800 DEBUG li/main.go:12 This is a DEBUG message
2022-11-19T10:07:36.394+0800 INFO li/main.go:13 This is an INFO message
//zap.NewProduction() 输出
{"level":"info","ts":1668823728.0574305,"caller":"li/main.go:13","msg":"This is an INFO message"}
{"level":"fatal","ts":1668823905.9411304,"caller":"li/main.go:14","msg":"This ia a fatal message","stacktrace":"main.main\n\tD:/goproject/src/hub-gin server/li/main.go:14\nruntime.main\n\tD:/go/src/runtime/proc.go:250"}
日志追加其他字段
记录日志的 logger.Xxx
方法,比如logger.Info
签名如下:
go
func (log *Logger) Info(msg string, fields ...Field)
其中 fields
是 zapcore.Field
类型,用来存储 key-value
,并记录 value
类型,不管是 zap.String
还是 zap.Int
底层都是 zapcore.Field
类型来记录的。zap
为每一种 Go 的内置类型都定义了对应的 zap.Xxx
方法,甚至还实现 zap.Any()
来支持 interface{}
。
所以如果想在日志中追加其他信息,往后追加参数就可以。
go
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
// 生产环境
{
logger, _ := zap.NewProduction()
defer logger.Sync() // 刷新 buffer,保证日志最终会被输出
url := "https://jianghushinian.cn/"
logger.Info("production failed to fetch URL",
zap.String("url", url), // 因为没有使用 interface{} 和反射机制,所以需要指定具体类型
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
}
// 开发环境
{
logger, _ := zap.NewDevelopment()
defer logger.Sync()
url := "https://jianghushinian.cn/"
logger.Debug("development failed to fetch URL",
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
}
}
shell
# zap.NewProduction() 输出信息
{"level":"info","ts":1679212318.10218,"caller":"zap/main.go:16","msg":"production failed to fetch URL","url":"https://jianghushinian.cn/","attempt":3,"backoff":1}
# zap.NewDevelopment() 输出信息
2023-03-19T15:51:58.102+0800 DEBUG zap/main.go:29 development failed to fetch URL {"url": "https://jianghushinian.cn/", "attempt": 3, "backoff": "1s"}
日志后面追加的 key-value
会被转换成 JSON
。
SugaredLogger
想让代码写起来更爽一些,zap
为我们提供了SugaredLogger
。
通过 logger.Sugar()
方法可以将一个 Logger
对象转换成一个 SugaredLogger
对象。
go
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
url := "https://jianghushinian.cn/"
sugar := logger.Sugar()
sugar.Infow("production failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Info("Info")
sugar.Infof("Infof: %s", url)
sugar.Infoln("Infoln")
}
shell
{"level":"info","ts":1679217743.5967638,"caller":"zap/sugar.go:15","msg":"production failed to fetch URL","url":"https://jianghushinian.cn/","attempt":3,"backoff":1}
{"level":"info","ts":1679217743.5969589,"caller":"zap/sugar.go:20","msg":"Info"}
{"level":"info","ts":1679217743.5969741,"caller":"zap/sugar.go:21","msg":"Infof: https://jianghushinian.cn/"}
{"level":"info","ts":1679217743.5969841,"caller":"zap/sugar.go:22","msg":"Infoln"}
SugaredLogger
提供了更人性化的接口,日志中追加 key-value
时不在需要 zap.String("url", url)
这种显式指明类型的写法,只需要保证 key
为 string
类型,value
则可以为任意类型,能够减少我们编写的代码量。
此外,为了满足不同需求,SugaredLogger
提供了四种方式输出日志:sugar.Xxx
、sugar.Xxxw
、sugar.Xxxf
、sugar.Xxxln
。
zap.Config
NewExample
和 NewProduction
使用的是json
格式输出,NewDevelopment
使用Text+空格
行的形式输出。
导致以上这些差异的原因是配置不同,我们来看下 zap.NewProduction
和 zap.NewDevelopment
的代码实现:
NewProduction
go
func NewProduction(options ...Option) (*Logger, error) {
return NewProductionConfig().Build(options...)
}
func NewProductionConfig() Config {
return Config{
Level: NewAtomicLevelAt(InfoLevel),
Development: false,
Sampling: &SamplingConfig{
Initial: 100,
Thereafter: 100,
},
Encoding: "json",
EncoderConfig: NewProductionEncoderConfig(),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}
NewDevelopment
go
func NewDevelopment(options ...Option) (*Logger, error) {
return NewDevelopmentConfig().Build(options...)
}
func NewDevelopmentConfig() Config {
return Config{
Level: NewAtomicLevelAt(DebugLevel),
Development: true,
Encoding: "console",
EncoderConfig: NewDevelopmentEncoderConfig(),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}
可以看到,两者在实现思路上是一样的,都是先创建一个配置对象 zap.Config
,然后再调用配置对象的 Build
方法来构建 Logger
。
zap.Config
它是一个结构体,定义如下:
go
type Config struct {
// 日志级别。会输出该级别及以上级别的日志。 动态改变日志级别,在运行时你可以安全改变日志级别
Level AtomicLevel `json:"level" yaml:"level"`
// 将日志记录器设置为开发模式,在 WarnLevel 及以上级别日志会包含堆栈跟踪信息
Development bool `json:"development" yaml:"development"`
// 禁用调用信息,值为 true 时,日志中将不再显示记录日志时所在的函数调用文件名和行号。
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// 是否完全禁止自动堆栈跟踪。默认情况下,在 development 中,warnlevel及以上日志级别会自动捕获堆栈跟踪信息
// 在 production 中,ErrorLevel 及以上也会自动捕获堆栈信息
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// 采样策略配置,单位为每秒,作用是限制日志在每秒内的输出数量,以此来防止全局的 CPU 和 I/O 负载过高。
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// 设置日志编码。可以设置为 console 和 json。也可以通过 RegisterEncoder 设置第三方编码格式
Encoding string `json:"encoding" yaml:"encoding"`
// 编码配置,决定了日志字段格式。
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// 配置日志输出位置,URLs 或文件路径,可配置多个。
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// zap 包内部出现错误的日志输出位置,URLs 或文件路径,可配置多个,默认 os.Stderr。
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// 初始化字段配置,该配置的字段会以结构化的形式打印在每条日志输出中。
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}
Sync
zap
会将日志先写入到内存中的缓冲区,然后再定期将缓冲区中的日志刷入到磁盘中,这样做的好处是能够提高日志的写入效率。
但是,如果程序异常退出,那么缓冲区中的日志就会丢失,为了避免这种情况,我们需要在程序退出前调用 日志记录器.Sync()
方法,将缓冲区中的日志刷入到磁盘中。
go
var logger *zap.Logger
func main() {
logger, _ = zap.NewProduction()
defer logger.Sync()
logger.Info("Info msg")
}
zap.New()
通过查看 zap.NewProduction()
和 zap.NewDevelopment()
两个构造函数源码,我们知道可以使用 zap.Config
对象的 Build
方法创建 Logger
对象。那么我们很容易能够想到,如果要定制 Logger
,只需要创建一个定制的 zap.Config
即可。
实现定制化,需要使用zap.New(...)
方法来手动传递所有配置,函数签名:
go
// New constructs a new Logger from the provided zapcore.Core and Options. If
// the passed zapcore.Core is nil, it falls back to using a no-op
// implementation.
//
// This is the most flexible way to construct a Logger, but also the most
// verbose. For typical use cases, the highly-opinionated presets
// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
// more convenient.
//
// For sample code, see the package-level AdvancedConfiguration example.
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...)
}
// Core is a minimal, fast logger interface. It's designed for library authors
// to wrap in a more user-friendly API.
type Core interface {
LevelEnabler
// With adds structured context to the Core.
With([]Field) Core
// Check determines whether the supplied Entry should be logged (using the
// embedded LevelEnabler and possibly some extra logic). If the entry
// should be logged, the Core adds itself to the CheckedEntry and returns
// the result.
//
// Callers must use Check before calling Write.
Check(Entry, *CheckedEntry) *CheckedEntry
// Write serializes the Entry and any Fields supplied at the log site and
// writes them to their destination.
//
// If called, Write should always log the Entry and Fields; it should not
// replicate the logic of Check.
Write(Entry, []Field) error
// Sync flushes buffered logs (if any).
Sync() error
}
可以看出zap.New(...)
方法接收两个参数,第一个参数是 zapcore.Core
,第二个参数是 Option
,Option
是一个函数类型,用来对 Logger
进行定制。
通过源码我们可以看到New
方法需要一个zapcore.Core
参数,而Core
是接口类型,那我们继续看如何实现Core
这个接口。
zapcore.Core
查看到源码中,如下:NewCore
可以返回一个ioCore
,然后再源码中ioCore
实现了Core
提供的几个接口
go
// NewCore creates a Core that writes logs to a WriteSyncer.
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
return &ioCore{
LevelEnabler: enab,
enc: enc,
out: ws,
}
}
type ioCore struct {
LevelEnabler
enc Encoder
out WriteSyncer
}
我们可以看得到zapcore.Core
需要三个配置------Encoder
,WriteSyncer
,LevelEnabler
。
1. Encoder
Encoder
是编码器,以什么样的格式写入日志。
目前,zap只支持两种编码器------JSON Encoder
和Console Encoder
。
go
// 调用zap中这两个方法来创建编码器
func NewJSONEncoder(cfg EncoderConfig) Encoder
func NewConsoleEncoder(cfg EncoderConfig) Encoder
比如,我们想要以JSON
形式返回 ,需要我们传入一个EncoderConfig
,这里我们使用zap库中自动配置好的zap.NewProductionEncoderConfig()
。
go
func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
关于Encoder
内置的配置有两种NewProductionEncoderConfig()
、NewDevelopmentEncoderConfig()
:
go
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,
}
}
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
// Keys can be anything except the empty string.
TimeKey: "T",
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
FunctionKey: zapcore.OmitKey,
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
对比来看,两者有很多不同的配置,比如生产环境下 EncodeTime
值为 zapcore.EpochTimeEncoder
,开发环境下 EncodeTime
值为 zapcore.ISO8601TimeEncoder
。这就是生产环境日志输出的时间格式为 Unix epoch
而开发环境日志输出的时间格式为 ISO8601
的原因。
zapcore.EncoderConfig
其他几个常用的配置项说明如下:
MessageKey
: 日志信息的键名,默认 msg。LevelKey
: 日志级别的键名,默认 level。TimeKey
: 日志时间的键名。EncodeLevel
: 日志级别的格式,默认为小写,如 info。
2. WriteSyncer
指定日志输出路径(文件 或 控制台 或者双向输出)。但是打开的类型不一样,文件打开的是io.writer
类型,而我们需要的是WriteSyncer
,所以我们使用zapcore.AddSync()
函数进行转换:
go
// core 三个参数之 日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
file, _ := os.Create("./server/zaplog_test/log.log")
return zapcore.AddSync(file)
}
3. LevelEnabler
哪种级别的日志将被写入(将写入该级别及以上的日志)。
通过配置上面的3个参数,我们就可以实现将日志输出到我们指定的文件中:
go
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var logger *zap.Logger
var SugaredLogger *zap.SugaredLogger
func InitLogger() {
encoder := getEncoder()
writerSyncer := getWriterSyncer()
core := zapcore.NewCore(encoder, writerSyncer, zap.DebugLevel)
logger = zap.New(core)
SugaredLogger = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getWriterSyncer() zapcore.WriteSyncer {
file, _ := os.Create("./server/zaplog_test/log.log") //指定日志输出文件位置
return zapcore.AddSync(file)
}
logger.WithOptions
我们还通过 logger.WithOptions()
为 Logger
对象增加了一个选项 zap.AddCallerSkip(100)
,这个选项的作用是指定在通过调用栈获得行号时跳过的调用深度,因为我们的函数调用栈并不是 100
层,所以会触发 zap
内部错误,zap
会将错误日志输出到 ErrorOutputPaths
配置指定的位置中,即 error.log
。
go
// 方式一:日志编辑器调用WithOptions方法,添加选项
logger = logger.WithOptions(zap.AddCallerSkip(100))
// 方式二:创建日志编辑器时,添加选项
logger = zap.New(core, zap.AddCallerSkip(100))
logger.WithOptions()
支持的选项如下:
WrapCore(f func(zapcore.Core) zapcore.Core)
: 使用一个新的 zapcore.Core 替换掉 Logger 内部原有的的 zapcore.Core 属性。Hooks(hooks ...func(zapcore.Entry) error)
: 注册钩子函数,用来在日志打印时同时调用注册的钩子函数。Fields(fs ...Field)
: 添加公共字段。ErrorOutput(w zapcore.WriteSyncer)
: 指定日志组件内部出现异常时的输出位置。Development()
: 将日志记录器设为开发模式,这将使 DPanic 级别日志记录错误后执行 panic()。AddCaller()
: 与 WithCaller(true) 等价。WithCaller(enabled bool)
: 指定是否在日志输出内容中增加调用信息,即文件名和行号。AddCallerSkip(skip int)
: 指定在通过调用栈获取文件名和行号时跳过的调用深度。AddStacktrace(lvl zapcore.LevelEnabler)
: 用来指定某个日志级别及以上级别输出调用堆栈。IncreaseLevel(lvl zapcore.LevelEnabler)
: 提高日志级别,如果传入的 lvl 比现有级别低,则不会改变日志级别。WithFatalHook(hook zapcore.CheckWriteHook)
: 当出现 Fatal 级别日志时调用的钩子函数。WithClock(clock zapcore.Clock)
: 指定日志记录器用来确定当前时间的 zapcore.Clock 对象,默认为 time.Now 的系统时钟。
其他使用场景
1.更改时间编码并添加调用者详细信息
鉴于我们对配置所做的更改,有下面两个问题:
- 时间是以非人类可读的方式展示,例如
1.572161051846623e+09
- 调用方函数的详细信息没有显示在日志中
我们要做的第一件事是覆盖默认的ProductionConfig()
,并进行以下更改:
- 修改时间编码器
- 在日志文件中使用大写字母记录日志级别
go
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
接下来,我们将修改 zap logger
代码,添加将调用函数信息记录到日志中的功能。为此,我们将在zap.New(..)
函数中添加一个Option
。
go
logger := zap.New(core, zap.AddCaller())
当使用这些修改过的 logger
配置调用上述部分的main()函数时,以下输出将打印在文件------test.log
中。
text
2019-10-27T15:33:29.855+0800 DEBUG logic/temp2.go:47 Trying to hit GET request for www.baidu.com
2019-10-27T15:33:29.855+0800 ERROR logic/temp2.go:50 Error fetching URL www.sogo.com : Error = Get www.baidu.com: unsupported protocol scheme ""
2019-10-27T15:33:29.856+0800 DEBUG logic/temp2.go:47 Trying to hit GET request for http://www.baidu.com
2019-10-27T15:33:30.125+0800 INFO logic/temp2.go:52 Success! statusCode = 200 OK for URL http://www.baidu.com
2.将日志同时输出到终端和文件
如果需要同时将日志输出到控制台终端和文件中,只需要改造一下zapcore.NewCore
即可,本质其实就是修改一下 WriteSyncer
,使用zapcore.NewMultiWriteSyncer
来设置多个输出对象
go
func getWriterSyncer() zapcore.WriteSyncer {
file, _ := os.Create("./server/zaplog_test/log.log") //指定日志输出文件位置
return zapcore.AddSync(file)
}
func InitLogger() {
encoder := getEncoder()
writerSyncer := getWriterSyncer()
//这里我们使用zapcore.NewMultiWriteSyncer()实现同时输出到多个对象中
// os.Stdout 为控制台输出
core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zap.DebugLevel)
logger = zap.New(core, zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号
SugaredLogger = logger.Sugar()
}
3.实现日志文件切割
如果将所有的日志记录都存储到同一个文件中,这显然是不可取的,不仅会导致内存过大,也不利于我们翻阅日志排查错误,因此实现日志文件的合理切割是十分必要的。但是很可惜,zap
并不支持切割日志文件,我们需要通过第三方库Lumberjack
实现切割操作
go
import "gopkg.in/natefinch/lumberjack.v2"
要在zap
中加入Lumberjack
支持,我们需要修改WriteSyncer
代码。我们将按照下面的代码修改getLogWriter()
函数:
go
// core 三个参数之 日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
//file, _ := os.Create("./server/zaplog_test/log.log")
//return zapcore.AddSync(file)
//引入第三方库 Lumberjack 加入日志切割功能
lumberWriteSyncer := &lumberjack.Logger{
Filename: "./server/zaplog_test/log.log",
MaxSize: 10, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(lumberWriteSyncer)
}
// core 三个参数之 日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
//file, _ := os.Create("./server/zaplog_test/log.log")
//return zapcore.AddSync(file)
//引入第三方库 Lumberjack 加入日志切割功能
lumberWriteSyncer := &lumberjack.Logger{
Filename: "./server/zaplog_test/log.log",
MaxSize: 10, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(lumberWriteSyncer)
}
这里我们设置的是 日志文件每 10MB 会切割并且在当前目录下最多保存 5 个日志文件
,并且会将旧文档保存30天
。
4.按照日志级别归档写入文件
为了管理人员的查询方便,一般我们需要将低于error
级别的放到info.log
,error
及以上严重级别日志存放到error.log
文件中,我们只需要改造一下zapcore.NewCore
方法的第3
个参数,然后将文件WriteSyncer
拆成info
和error
两个即可,也就是将上文的getWriterSyncer()
拆分开:
go
/*
// core 三个参数之 日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
//file, _ := os.Create("./server/zaplog_test/log.log")
//return zapcore.AddSync(file)
//引入第三方库 Lumberjack 加入日志切割功能
lumberWriteSyncer := &lumberjack.Logger{
Filename: "./server/zaplog_test/log.log",
MaxSize: 10, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(lumberWriteSyncer)
}
*/
// 记录error以下日志级别的文件
func getInfoWriterSyncer() zapcore.WriteSyncer {
//file, _ := os.Create("./server/zaplog/log.log")
// 或者将上面的NewMultiWriteSyncer放到这里来,进行返回
//return zapcore.AddSync(file)
//引入第三方库 Lumberjack 加入日志切割功能
infoLumberIO := &lumberjack.Logger{
Filename: "./server/zaplog/info.log",
MaxSize: 10, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(infoLumberIO)
}
//记录error及以上日志级别的文件
func getErrorWriterSyncer() zapcore.WriteSyncer {
//引入第三方库 Lumberjack 加入日志切割功能
lumberWriteSyncer := &lumberjack.Logger{
Filename: "./server/zaplog/error.log",
MaxSize: 10, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(lumberWriteSyncer)
}
在使用时只需要对日志级别进行判断,从而将不同级别的文件写入到不同的日志文件中
go
var logger *zap.Logger
var SugaredLogger *zap.SugaredLogger
func InitLogger() {
//获取编码器
encoder := getEncoder()
//对日志级别进行判断、分类
highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //error级别
return lev >= zap.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
return lev < zap.ErrorLevel && lev >= zap.DebugLevel
})
//info文件WriteSyncer
infoFileWriteSyncer := getInfoWriterSyncer()
//error文件WriteSyncer
errorFileWriteSyncer := getErrorWriterSyncer()
//生成core
//multiWriteSyncer := zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)) //AddSync将io.Writer转换成WriteSyncer的类型
//同时输出到控制台 和 指定的日志文件中
infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority)
errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority)
//将infocore 和 errcore 加入core切片
var coreArr []zapcore.Core
coreArr = append(coreArr, infoFileCore)
coreArr = append(coreArr, errorFileCore)
//生成logger
logger = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号
SugaredLogger = logger.Sugar()
}
这样修改之后,info
和debug
级别的日志就存放到info.log
,error
级别的日志单独放到error.log
文件中了
自定义配置
创建自定义的配置对象,除了在代码中指定配置参数,也可以将这些配置项写入到 JSON
文件中,然后通过 json.Unmarshal
的方式将配置绑定到 zap.Config
,可以参考官方示例。
go
package main
import (
"encoding/json"
"go.uber.org/zap"
)
func main() {
// For some users, the presets offered by the NewProduction, NewDevelopment,
// and NewExample constructors won't be appropriate. For most of those
// users, the bundled Config struct offers the right balance of flexibility
// and convenience. (For more complex needs, see the AdvancedConfiguration
// example.)
//
// See the documentation for Config and zapcore.EncoderConfig for all the
// available options.
rawJSON := []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout", "/tmp/logs"],
"errorOutputPaths": ["stderr"],
"initialFields": {"foo": "bar"},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase"
}
}`)
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
logger := zap.Must(cfg.Build())
defer logger.Sync()
logger.Info("logger construction succeeded")
}
// 输出
// {"level":"info","message":"logger construction succeeded","foo":"bar"}
完结撒花🎉🎉🎉,你又进步了一点点。
朋友,如果觉得本文章对你有帮助,可以留个赞👍再走嘛。
赠人玫瑰手留余香!先谢谢啦 ᕦ( •̀∀•́)ᕤ