如果你想学到更多实用知识。
可以关注我的公众号:【前端驿站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"}
完结撒花🎉🎉🎉,你又进步了一点点。
朋友,如果觉得本文章对你有帮助,可以留个赞👍再走嘛。
赠人玫瑰手留余香!先谢谢啦 ᕦ( •̀∀•́)ᕤ