Go | zap
1. 简介
那些介绍、性能比较直接看参考中zap链接,这里只介绍该日志库用法,方便快速上手。
package mainimport ("go.uber.org/zap"
)func main() {logger, _ := zap.NewProduction()defer logger.Sync()logger.Info("hello world",zap.String("name", "yimt"),)// 可以将结构化日志转为fmt包形式sugar := logger.Sugar()sugar.Infof("name %s", "yimt")
}
输出
{"level":"info","ts":1684460836.884597,"caller":"hello-go/main.go:11","msg":"hello world","name":"yimt"}
{"level":"info","ts":1684460836.88468,"caller":"hello-go/main.go:17","msg":"name yimt"}
2. NewDevelopment & NewProduction区别
接下来通过查看NewDevelopment
与NewProduction
实现,简单了解下zap的配置,为后续学习zapcore.Core
打下基础。
2.1. 实现
NewDevelopment
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"},}
}
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,}
}
NewProduction
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"},}
}
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.2. 执行结果
NewDevelopment
func main() {logger, _ := zap.NewDevelopment()defer logger.Sync()logger.Info("hello world",zap.String("name", "yimt"),)
}
输出
2023-05-17T14:07:09.722+0800 INFO hello-go/main.go:11 hello world {"name": "yimt"}
NewProduction
func main() {logger, _ := zap.NewProduction()defer logger.Sync()logger.Info("hello world",zap.String("name", "yimt"),)
}
输出
{"level":"info","ts":1684303547.31098,"caller":"hello-go/main.go:11","msg":"hello world","name":"yimt"}
3. zapcore.Core接口
zapcore.NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler)
zapcore.NewNopCore()
zapcore.NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error)
zapcore.NewTee()
3.1. zapcore.NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler)
3.1.1. Encoder
zapcore.NewJSONEncoder(cfg EncoderConfig)
:以JSON形式编码。zapcore.NewConsoleEncoder(cfg EncoderConfig)
:以console形式编码。
zapcore.EncoderConfig
主要用于配置日志编码,包括时间格式,日志键名,每条日志是否换行等。
- zap.NewDevelopmentEncoderConfig()
- zap.NewProductionEncoderConfig()
- zapcore.EncoderConfig{}:可以自定义配置。
控制日志编码格式,正常控制台显示console形式编码,日志文件记录JSON形式编码。
3.1.2. zapcore.WriteSyncer
zapcore.AddSync(f)
:指定日志输出位置。
控制日志输出位置,主要有控制台和日志文件。
3.1.3. zapcore.LevelEnabler
zap.NewAtomicLevel()
:可以运行过程中线程安全的调节日志等级。zap.LevelEnablerFunc
:以回调形式控制日志等级。
用于控制日志等级。
3.1.4. 演示
package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {core := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), zap.NewAtomicLevel())logger := zap.New(core)defer logger.Sync()logger.Info("hello world")
}
控制台输出
{"level":"info","ts":1684459049.47701,"msg":"hello world"}
3.2. zapcore.NewNopCore()
package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore"
)func main() {logger := zap.New(zapcore.NewNopCore())defer logger.Sync()logger.Info("hello world")
}
执行完什么都不会发生,可以直接看NewNopCore()
内实现,上面Core所有功能都不处理。
type nopCore struct{}// NewNopCore returns a no-op Core.
func NewNopCore() Core { return nopCore{} }
func (nopCore) Enabled(Level) bool { return false }
func (n nopCore) With([]Field) Core { return n }
func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce }
func (nopCore) Write(Entry, []Field) error { return nil }
func (nopCore) Sync() error { return nil }
3.3. zapcore.NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error)
实现
func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) {for l := _maxLevel; l >= _minLevel; l-- {if !core.Enabled(l) && level.Enabled(l) {return nil, fmt.Errorf("invalid increase level, as level %q is allowed by increased level, but not by existing core", l)}}return &levelFilterCore{core, level}, nil
}
直接在已有一个Core
下降1个日志等级,以显示更多日志。
3.4. zapcore.NewTee()
上面示例都有一个问题,日志只能输出到一个Core
。在有的时候我们需要日志以console
形式输出到控制台,以json
形式输出到日志文件时就用到了NewTee
,可以将日志一次输入到多个Core
处理。
package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {f, err := os.Create("log.txt")if err != nil {panic(err)}// 日志已json形式输出文件outputFileCore := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(f), zap.NewAtomicLevel())// 日志已console形式输出到控制台outputConsoleCore := zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), zapcore.AddSync(os.Stdout), zap.NewAtomicLevel())tee := zapcore.NewTee(outputFileCore, outputConsoleCore)logger := zap.New(tee)defer logger.Sync()logger.Info("hello world")
}
log.txt
{"level":"info","ts":1684459649.253505,"msg":"hello world"}
控制台
2023-05-19T09:27:29.253+0800 INFO hello world
4. 输出日志文件问题
上面示例虽然可以输出日志文件,但是还存在以下问题。
- 单个日志文件大小无法限制,单个日志文件过大最直观的麻烦是我们用文件编辑器打开经常未响应。
- 日志文件不会自己删除,在生产服务器可能会存在大量日志文件,占用硬盘空间。应该有不少人在MySQL的
binlog
日志文件上踩过坑吧。 - 旧日志压缩存储,节约硬盘空间。
下面就介绍lumberjack
库,帮助我们自己管理日志文件,直接看下面示例就可以了。
package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2"
)func main() {writeSyncer := zapcore.AddSync(&lumberjack.Logger{Filename: "log.txt",MaxSize: 500, // megabytesMaxBackups: 3,MaxAge: 28, //daysCompress: true, // disabled by default})core := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writeSyncer, zap.NewAtomicLevel())logger := zap.New(core)defer logger.Sync()logger.Info("hello world")
}
5. 最终示例
功能
- 同时输出控制台和日志文件
- 日志文件达到限制大小自动创建新日志文件。
- 自己删除过期日志。
- 旧日志文件自动压缩。
package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2""os"
)func main() {writeSyncer := zapcore.AddSync(&lumberjack.Logger{Filename: "log.txt",MaxSize: 500, // megabytesMaxBackups: 3,MaxAge: 28, //daysCompress: true, // disabled by default})// 日志已json形式输出文件outputFileCore := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writeSyncer, zap.NewAtomicLevel())// 日志已console形式输出到控制台outputConsoleCore := zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), zapcore.AddSync(os.Stdout), zap.NewAtomicLevel())tee := zapcore.NewTee(outputFileCore, outputConsoleCore)logger := zap.New(tee)defer logger.Sync()logger.Info("hello world")
}
6. 参考
- zap
- lumberjack