golang整合日志zap的实现示例
在许多Go语言项目中,我们需要一个好的日志记录器能够提供下面这些功能:
能够将事件记录到文件中,而不是应用程序控制台;日志切割-能够根据文件大小、时间或间隔等来切割日志文件;支持不同的日志级别。例如INFO,DEBUG,ERROR等;能够打印基本信息,如调用文件/函数名和行号,日志时间等;日志分级:有 Debug,Info,Warn,Error,DPanic,Panic,Fatal 等日志记录结构化:日志内容记录是结构化的,比如 json 格式输出自定义格式:用户可以自定义输出的日志格式自定义公共字段:用户可以自定义公共字段,大家输出的日志内容就共同拥有了这些字段调试:可以打印文件名、函数名、行号、日志时间等,便于调试程序自定义调用栈级别:可以根据日志级别输出它的调用栈信息Namespace:日志命名空间。定义命名空间后,所有日志内容就在这个命名空间下。命名空间相当于一个文件夹支持 hook 操作go get -u go.uber.org/zap
package mainimport "go.uber.org/zap"var logger *zap.Loggerfunc main() {logger, _ = zap.NewProduction() // 使用生产环境 // logger, _ := zap.NewDevelopment() // 使用开发环境defer logger.Sync() // 刷新缓存 suger := logger.Sugar() // 开启日志语法糖 suger.Debug("我是Debug级别的日志")suger.Debugw("我是Debug级别的日志", "key1", "value1", "key2", "value2")suger.Debugf("我是Debug级别的日志%s", "value")suger.Warn("我是Warn级别的日志")suger.Warnw("我是Warn级别的日志", "key1", "value1", "key2", "value2")suger.Warnf("我是Warn级别的日志%s", "value")suger.Info("我是Info级别的日志")suger.Infow("我是Info级别的日志", "key1", "value1", "key2", "value2")suger.Infof("我是Info级别的日志 %s", "info")suger.Error("我是Error级别的日志")suger.Errorw("我是Error级别的日志", "key1", "value1", "key2", "value2")suger.Errorf("我是Error级别的日志 %s", "value")suger.Panic("我是Panic级别的日志")suger.Panicw("我是Panic级别的日志", "key1", "value1", "key2", "value2")suger.Panicf("我是Panic级别的日志 %s", "value") // 使用不带语法糖的日志 logger.Debug("我是Debug级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Warn("我是Warn级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Info("我是Info级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Error("我是Error级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Panic("我是Panic级别的日志", zap.String("key", "value"), zap.Int("num", 10))}
zap 为我们提供了三种快速创建 logger 的方法: zap.NewProduction(),zap.NewDevelopment(),zap.NewExample()
Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中
NewExample 构建一个 logger,专门为在 zap 的测试示例使用。它将 DebugLevel 及以上日志用 JSON 格式标准输出,但它省略了时间戳和调用函数,以保持示例输出的简短和确定性
因为在这个方法里,zap 已经定义好了日志配置项部分默认值
// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127func NewExample(options ...Option) *Logger {encoderCfg := zapcore.EncoderConfig{ MessageKey: "msg", // 日志内容key:val, 前面的key设为msgLevelKey: "level", // 日志级别的key设为levelNameKey: "logger", // 日志名EncodeLevel: zapcore.LowercaseLevelEncoder, //日志级别,默认小写EncodeTime: zapcore.ISO8601TimeEncoder, // 日志时间EncodeDuration: zapcore.StringDurationEncoder,}core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)return New(core).WithOptions(options...)}
使用例子
package mainimport ("go.uber.org/zap")func main() {logger := zap.NewExample()logger.Debug("this is debug message")}
NewDevelopment() 构建一个开发使用的 Logger
使用例子
package mainimport ("time""go.uber.org/zap")func main() {logger, _ := zap.NewDevelopment()defer logger.Sync()logger.Info("failed to fetch url",// 强类型字段zap.String("url", "http://example.com"),zap.Int("attempt", 3),zap.Duration("duration", time.Second),)logger.With(// 强类型字段zap.String("url", "http://development.com"),zap.Int("attempt", 4),zap.Duration("duration", time.Second*5),).Info("[With] failed to fetch url")}
NewProduction() 构建了一个合理的 Prouction 日志记录器,它将 info 及以上的日志内容以 JSON 格式记写入标准错误里
使用例子
package mainimport ("time""go.uber.org/zap")func main() {logger, _ := zap.NewProduction()defer logger.Sync()url := "http://zap.uber.io"sugar := logger.Sugar()sugar.Infow("failed to fetch URL","url", url,"attempt", 3,"time", time.Second,)sugar.Infof("Failed to fetch URL: %s", url)// 或更简洁 Sugar() 使用// sugar := zap.NewProduction().Sugar()// defer sugar.Sync()}
在这 3 个函数中,可以传入一些配置项。
func NewExample(options Option) *Logger
package mainimport ("os""go.uber.org/zap""go.uber.org/zap/zapcore")func main() {encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())file, _ := os.OpenFile("./log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)syncFile := zapcore.AddSync(file)syncConsole := zapcore.AddSync(os.Stderr)sync := zapcore.NewMultiWriteSyncer(syncConsole, syncFile)core := zapcore.NewCore(encoder, sync, zapcore.InfoLevel)logger := zap.New(core)logger.Info("info 日志", zap.Int("line", 1))}
Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore")func main() {logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {fmt.Println("[zap.Hooks]test Hooks")return nil}))defer logger.Sync()logger.Info("test output")logger.Warn("warn info")}
快速构建 logger 日志记录器最简单的方法就是用 zap 预定义了配置的方法:NewExample(), NewProduction() 和NewDevelopment(),这 3 个方法通过单个函数调用就可以构建一个日志计记录器,也可以简单配置
Config 配置项源码
// zap v1.24.0type Config struct { // 动态改变日志级别,在运行时你可以安全改变日志级别Level AtomicLevel `json:"level" yaml:"level"` // 将日志记录器设置为开发模式,在 WarnLevel 及以上级别日志会包含堆栈跟踪信息Development bool `json:"development" yaml:"development"` // 在日志中停止调用函数所在文件名、行数DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` // 完全禁止自动堆栈跟踪。默认情况下,在 development 中,warnlevel及以上日志级别会自动捕获堆栈跟踪信息 // 在 production 中,ErrorLevel 及以上也会自动捕获堆栈信息DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` // 设置采样策略。没有 SamplingConfing 将禁止采样Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` // 设置日志编码。可以设置为 console 和 json。也可以通过 RegisterEncoder 设置第三方编码格式Encoding string `json:"encoding" yaml:"encoding"` // 为encoder编码器设置选项。详细设置信息在 zapcore.zapcore.EncoderConfigEncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` // 日志输出地址可以是一个 URLs 地址或文件路径,可以设置多个OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` // 错误日志输出地址。默认输出标准错误信息ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` // 可以添加自定义的字段信息到 root logger 中。也就是每条日志都会携带这些字段信息,公共字段InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`}
EncoderConfig 结构源码,它里面也有很多配置选项
// zapcore@v1.24.0type EncoderConfig struct { // 为log entry设置key。如果 key 为空,那幺在日志中的这部分信息也会省略MessageKey string `json:"messageKey" yaml:"messageKey"`//日志信息的健名,默认为msgLevelKey string `json:"levelKey" yaml:"levelKey"`//日志级别的健名,默认为levelTimeKey string `json:"timeKey" yaml:"timeKey"`//记录日志时间的健名,默认为timeNameKey string `json:"nameKey" yaml:"nameKey"`CallerKey string `json:"callerKey" yaml:"callerKey"`FunctionKey string `json:"functionKey" yaml:"functionKey"`StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"`LineEnding string `json:"lineEnding" yaml:"lineEnding"` // 日志编码的一些设置项EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` // 与其它编码器不同, 这个编码器可选EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` // 配置 interface{} 类型编码器。如果没设置,将用 json.Encoder 进行编码NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` // 配置 console 中字段分隔符。默认使用 tab ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`}type Entry struct {Level LevelTime time.TimeLoggerName stringMessage stringCaller EntryCallerStack string}zap 提供了 2 种日志记录器:`SugaredLogger` 和 `Logger`
在需要性能但不是很重要的情况下,使用 SugaredLogger 较合适。它比其它结构化日志包快 4-10 倍,包括 结构化日志和 printf 风格的 API
使用: suger.[日志级别]x
w 支持键值对方式传入日志
f 支持%s 方式插值
suger := logger.Sugar() // 开启日志语法糖 suger.Debug("我是Debug级别的日志")suger.Debugw("我是Debug级别的日志", "key1", "value1", "key2", "value2")suger.Debugf("我是Debug级别的日志%s", "value")suger.Warn("我是Warn级别的日志")suger.Warnw("我是Warn级别的日志", "key1", "value1", "key2", "value2")suger.Warnf("我是Warn级别的日志%s", "value")suger.Info("我是Info级别的日志")suger.Infow("我是Info级别的日志", "key1", "value1", "key2", "value2")suger.Infof("我是Info级别的日志 %s", "info")suger.Error("我是Error级别的日志")suger.Errorw("我是Error级别的日志", "key1", "value1", "key2", "value2")suger.Errorf("我是Error级别的日志 %s", "value")suger.Panic("我是Panic级别的日志")suger.Panicw("我是Panic级别的日志", "key1", "value1", "key2", "value2")suger.Panicf("我是Panic级别的日志 %s", "value")
当性能和类型安全很重要时,请使用 Logger。它比 SugaredLogger 更快,分配的资源更少,但它只支持结构化日志和强类型字段
第一个参数打印日志名,后边支持传入可变参, zapcore.Field类型
zap.String( key , value ) 表示打印key为字符串 value为字符串的 map
logger.Debug("我是Debug级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Warn("我是Warn级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Info("我是Info级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Error("我是Error级别的日志", zap.String("key", "value"), zap.Int("num", 10))logger.Panic("我是Panic级别的日志", zap.String("key", "value"), zap.Int("num", 10))
cfg := zap.NewProductionConfig()cfg.OutputPaths = []string{ "./OUTPUT.log", "stderr", "stdout",}logger, _ = cfg.Build()logger.Debug("打印日志到文件")
lumberjack 这个库是按照日志大小切割日志文件。
go get -u github.com/natefinch/lumberjack@v2
log.SetOutput( lumberjack.Logger{ Filename: "/var/log/myapp/foo.log", // 文件位置 MaxSize: 500, // megabytes,M 为单位,达到这个设置数后就进行日志切割 MaxBackups: 3, // 保留旧文件最大份数 MaxAge: 28, //days , 旧文件最大保存天数 Compress: true, // disabled by default,是否压缩日志归档,默认不压缩})
参照它的文档和结合上面自定义配置
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2")func main() {lumberjacklogger := lumberjack.Logger{Filename: "./log-rotate-test.json",MaxSize: 1, // megabytesMaxBackups: 3,MaxAge: 28, //daysCompress: true, // disabled by default}defer lumberjacklogger.Close()config := zap.NewProductionEncoderConfig()config.EncodeTime = zapcore.ISO8601TimeEncoder // 设置时间格式fileEncoder := zapcore.NewJSONEncoder(config)core := zapcore.NewCore(fileEncoder, //编码设置zapcore.AddSync(lumberjacklogger), //输出到文件zap.InfoLevel, //日志等级)logger := zap.New(core)defer logger.Sync() // 测试分割日志for i := 0; i 8000; i++ {logger.With(zap.String("url", fmt.Sprintf("www.test%d.com", i)),zap.String("name", "jimmmyr"),zap.Int("age", 23),zap.String("agradege", "no111-000222"),).Info("test info ")}}
zap提供了 2 种全局 Logger,一个是 zap.Logger,调用 zap.L() 获取;
另外一个是 zap.SugaredLogger ,调用 zap.S() 获取
直接调用 zap.L() 或 zap.S() 记录日志的话,它是不会记录任何日志信息。需要调用 ReplaceGlobals() 函数将它设置为全局 Logger。
ReplaceGlobals 替换全局 Logger 和 SugaredLogger,并返回一个函数来恢复原始值
简单使用例子
package mainimport ("go.uber.org/zap")func main() {// 直接调用是不会记录日志信息的,所以下面日志信息不会输出zap.L().Info("no log info")zap.S().Info("no log info [sugared]")logger := zap.NewExample()defer logger.Sync()zap.ReplaceGlobals(logger) // 全局logger,zap.L() 和 zap.S() 需要调用 ReplaceGlobals 函数才会记录日志信息zap.L().Info("log info")zap.S().Info("log info [sugared]")}
golang整合日志zap的实现示例