Golang日志操作库zap的使用详解

2024-04-18 0 313
目录
  • 一、简介
    • zap的特性
    • 高性能介绍
  • 二、quickstart快速开始
    • 三、NewExample/NewDevelopment/NewProduction使用
      • NewExample()使用
      • NewDevelopment()使用
      • NewProduction()使用
      • 使用配置
    • 四、logger和sugaredlogger区别
      • 五、自定义配置
        • 配置结构说明
        • 例子1:基本配置
        • 例子2:高级配置
        • 例子3:日志写入文件
        • 例子4:根据日志级别写入不同文件
      • 六、Hook和Namespace
        • 七、日志切割归档
          • 八、其它方法使用
            • 全局 Logger
            • 与标准日志库搭配
            • 一段代码中使用log另外的使用zap
            • 输出调用堆栈
            • 输出文件名和行号
          • 九、zap使用总结
            • 十、Demo源码地址

              一、简介

              zap是uber开源的一个高性能,结构化,分级记录的日志记录包。

              go1.20.2

              zap v1.24.0

              zap的特性

              • 高性能:zap 对日志输出进行了多项优化以提高它的性能
              • 日志分级:有 Debug,Info,Warn,Error,DPanic,Panic,Fatal 等
              • 日志记录结构化:日志内容记录是结构化的,比如 json 格式输出
              • 自定义格式:用户可以自定义输出的日志格式
              • 自定义公共字段:用户可以自定义公共字段,大家输出的日志内容就共同拥有了这些字段
              • 调试:可以打印文件名、函数名、行号、日志时间等,便于调试程序
              • 自定义调用栈级别:可以根据日志级别输出它的调用栈信息
              • Namespace:日志命名空间。定义命名空间后,所有日志内容就在这个命名空间下。命名空间相当于一个文件夹
              • 支持 hook 操作

              高性能介绍

              与其它日志库对比

              看github官网的对比图,下面的对比图来自:https://github.com/uber-go/zap#performance

              Log a message and 10 fields:

              PackageTimeTime % to zapObjects Allocated⚡ zap2900 ns/op+0%5 allocs/op⚡ zap (sugared)3475 ns/op+20%10 allocs/opzerolog10639 ns/op+267%32 allocs/opgo-kit14434 ns/op+398%59 allocs/oplogrus17104 ns/op+490%81 allocs/opapex/log32424 ns/op+1018%66 allocs/oplog1533579 ns/op+1058%76 allocs/op

              Log a message with a logger that already has 10 fields of context:

              PackageTimeTime % to zapObjects Allocated⚡ zap373 ns/op+0%0 allocs/op⚡ zap (sugared)452 ns/op+21%1 allocs/opzerolog288 ns/op-23%0 allocs/opgo-kit11785 ns/op+3060%58 allocs/oplogrus19629 ns/op+5162%70 allocs/oplog1521866 ns/op+5762%72 allocs/opapex/log30890 ns/op+8182%55 allocs/op

              Log a static string, without any context orprintf-style templating:

              PackageTimeTime % to zapObjects Allocated⚡ zap381 ns/op+0%0 allocs/op⚡ zap (sugared)410 ns/op+8%1 allocs/opzerolog369 ns/op-3%0 allocs/opstandard library385 ns/op+1%2 allocs/opgo-kit606 ns/op+59%11 allocs/oplogrus1730 ns/op+354%25 allocs/opapex/log1998 ns/op+424%7 allocs/oplog154546 ns/op+1093%22 allocs/op

              做了哪些优化

              基于反射的序列化和字符串格式化,它们都是 CPU 密集型计算且分配很多小的内存。具体到 Go 语言中,使用 encoding/json 和 fmt.Fprintf 格式化 interface{} 会使程序性能降低。

              Zap 咋解决呢?Zap 使用一个无反射、零分配的 JOSN 编码器,基础 Logger 尽可能避免序列化开销和内存分配开销。在此基础上,zap 还构建了更高级的 SuggaredLogger。

              二、quickstart快速开始

              zap 安装:

              go get -u go.uber.org/zap

              zap 提供了 2 种日志记录器:SugaredLogger和Logger。

              在需要性能但不是很重要的情况下,使用 SugaredLogger 较合适。它比其它结构化日志包快 4-10 倍,包括 结构化日志和 printf 风格的 API。看下面使用 SugaredLogger 例子:

              logger, _ := zap.NewProduction()
              defer logger.Sync() // zap底层有缓冲。在任何情况下执行 defer logger.Sync() 是一个很好的习惯
              sugar := logger.Sugar()
              sugar.Infow(\”failed to fetch URL\”,
              // 字段是松散类型,不是强类型
              \”url\”, url,
              \”attempt\”, 3,
              \”backoff\”, time.Second,
              )
              sugar.Infof(\”Failed to fetch URL: %s\”, url)

              当性能和类型安全很重要时,请使用 Logger。它比 SugaredLogger 更快,分配的资源更少,但它只支持结构化日志和强类型字段。

              logger, _ := zap.NewProduction()
              defer logger.Sync()
              logger.Info(\”failed to fetch URL\”,
              // 字段是强类型,不是松散类型
              zap.String(\”url\”, url),
              zap.Int(\”attempt\”, 3),
              zap.Duration(\”backoff\”, time.Second),
              )

              三、NewExample/NewDevelopment/NewProduction使用

              zap 为我们提供了三种快速创建 logger 的方法:zap.NewProduction(),zap.NewDevelopment(),zap.NewExample()。

              见名思义,Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中。这三种方法都预先设置好了配置信息。

              NewExample()使用

              NewExample 构建一个 logger,专门为在 zap 的测试示例使用。它将 DebugLevel 及以上日志用 JSON 格式标准输出,但它省略了时间戳和调用函数,以保持示例输出的简短和确定性。

              为什么说zap.NewExample()是 zap 为我们提供快速创建 logger 的方法呢?

              因为在这个方法里,zap 已经定义好了日志配置项部分默认值。来看它的代码:

              // https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127
              func NewExample(options …Option) *Logger {
              encoderCfg := zapcore.EncoderConfig{
              MessageKey: \”msg\”, // 日志内容key:val, 前面的key设为msg
              LevelKey: \”level\”, // 日志级别的key设为level
              NameKey: \”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 main

              import (
              \”go.uber.org/zap\”
              )

              func main() {
              logger := zap.NewExample()
              logger.Debug(\”this is debug message\”)
              logger.Info(\”this is info message\”)
              logger.Info(\”this is info message with fileds\”,
              zap.Int(\”age\”, 37),
              zap.String(\”agender\”, \”man\”),
              )
              logger.Warn(\”this is warn message\”)
              logger.Error(\”this is error message\”)
              }

              输出:

              {"level":"debug","msg":"this is debug message"} {"level":"info","msg":"this is info message"} {"level":"info","msg":"this is info message with fileds","age":37,"agender":"man"} {"level":"warn","msg":"this is warn message"} {"level":"error","msg":"this is error message"}

              NewDevelopment()使用

              NewDevelopment() 构建一个开发使用的 Logger,它以人性化的格式将 DebugLevel 及以上日志信息输出。它的底层使用

              NewDevelopmentConfig().Build(…Option)构建。它的日志格式各种设置在函数NewDevelopmentEncoderConfig()里,想查看详情设置,请点进去查看。

              使用例子:

              package main

              import (
              \”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\”)
              }

              输出:

              2023-03-22T16:02:45.760+0800 INFO zapdemos/newdevelopment1.go:13 failed to fetch url {"url": "http://example.com", "attempt": 3, "duration": "1s"} 2023-03-22T16:02:45.786+0800 INFO zapdemos/newdevelopment1.go:25 [With] failed to fetch url {"url": "http://development.com", "attempt": 4, "duration": "5s"}

              上面日志输出了文件名和行号,NewExample() 没有

              NewProduction()使用

              NewProduction() 构建了一个合理的 Prouction 日志记录器,它将 info 及以上的日志内容以 JSON 格式记写入标准错误里。

              它的底层使用NewProductionConfig().Build(…Option)构建。它的日志格式设置在函数NewProductionEncoderConfig里。

              使用例子:

              package main

              import (
              \”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()
              }

              输出:

              {"level":"info","ts":1679472893.2944522,"caller":"zapdemos/newproduction1.go:16","msg":"failed to fetch URL","url":"http://zap.uber.io","attempt":3,"time":1} {"level":"info","ts":1679472893.294975,"caller":"zapdemos/newproduction1.go:22","msg":"Failed to fetch URL: http://zap.uber.io"}

              上面日志输出了文件名和行号,NewExample() 没有

              使用配置

              在这 3 个函数中,可以传入一些配置项。为什么能传入配置项?我们来看看NewExample()函数定义:

              func NewExample(options …Option) *Logger

              它的函数传参有一个…Option选项,是一个 interface 类型,它关联的是 Logger struct。只要返回 Option 就可以传进 NewExample() 里。在zap/options.go文件中可以看到很多返回 Option 的函数,也就是说这些函数都可以传入 NewExample 函数里。这里用到了 Go 里面的一个编码技巧,函数选项模式。

              zap.Fields()添加字段到 Logger 中:

              package main

              import (
              \”go.uber.org/zap\”
              )

              func main() {
              logger, _ := zap.NewProduction(zap.Fields(
              zap.String(\”log_name\”, \”testlog\”),
              zap.String(\”log_author\”, \”prometheus\”),
              ))
              defer logger.Sync()

              logger.Info(\”test fields output\”)

              logger.Warn(\”warn info\”)
              }

              输出:

              {"level":"info","ts":1679477929.842166,"caller":"zapdemos/fields.go:14","msg":"test fields output","log_name":"testlog","log_author":"prometheus"} {"level":"warn","ts":1679477929.842166,"caller":"zapdemos/fields.go:16","msg":"warn info","log_name":"testlog","log_author":"prometheus"}

              zap.Hook()添加回调函数:

              Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数。

              package main

              import (
              \”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\”)
              }

              输出:

              {"level":"info","msg":"test output"} [zap.Hooks]test Hooks {"level":"warn","msg":"warn info"} [zap.Hooks]test Hooks

              四、logger和sugaredlogger区别

              从上面例子中看出,zap 有 2 种格式化日志方式:logger 和 sugared logger。

              sugared logger:

              • 它有很好的性能,比一般日志包快 4-10 倍。
              • 支持结构化的日志。
              • 支持 printf 风格的日志。
              • 日志字段不需要定义类型

              logger(没有sugar)

              • 它的性能比 sugared logger 还要快。
              • 它只支持强类型的结构化日志。
              • 它应用在对性能更加敏感日志记录中,它的内存分配次数更少。
              • 比如如果每一次内存分配都很重要的话可以使用这个。对类型安全有严格要求也可以使用这个。

              logger 和 sugaredlogger 相互转换:

              // 创建 logger
              logger := zap.NewExample()
              defer logger.Sync()

              // 转换 SugaredLogger
              sugar := logger.Sugar()

              // 转换 logger
              plain := sugar.Desugar()

              怎么快速构建一个 logger 呢?有下面种几种方法:

              • zap.NewProduction()
              • zap.NewDevelopment()
              • zap.Example()

              主要区别:

              • 记录日志信息和结构不同。
              • Example 和 Production 是 json 格式输出,Development 是普通一行格式输出,如果后面带有字段输出话用json格式。

              相同点:

              • 默认情况下都会打印日志信息到 console 界面
              • 都是通过 logger 调用 Info、Error 等方法

              怎么选择:

              • 需要不错的性能但不是很重要的情况下,可以选择 sugaredlogger。它支持结构化日志和 printf 风格的日志记录。sugaredlogger 的日志记录是松散类型的,不是强类型,能接受可变数量的键值对。如果你要用强类型字段记录,可以使用 SugaredLogger.With 方法。
              • 如果是每次或每微秒记录日志都很重要情况下,可以使用 logger,它比 sugaredlogger 每次分配内存更少,性能更高。但它仅支持强类型的结构化日志记录。

              五、自定义配置

              快速构建 logger 日志记录器最简单的方法就是用 zap 预定义了配置的方法:NewExample(), NewProduction()和NewDevelopment(),这 3 个方法通过单个函数调用就可以构建一个日志计记录器,也可以简单配置。

              但是有的项目需要更多的定制,怎么办?zap 的Config结构和 zapcore 的EncoderConfig结构可以帮助你,让你能够进行自定义配置。

              配置结构说明

              Config 配置项源码:

              // zap v1.24.0
              type 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.EncoderConfig
              EncoderConfig 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.0
              type EncoderConfig struct {
              // 为log entry设置key。如果 key 为空,那么在日志中的这部分信息也会省略
              MessageKey string `json:\”messageKey\” yaml:\”messageKey\”`//日志信息的健名,默认为msg
              LevelKey string `json:\”levelKey\” yaml:\”levelKey\”`//日志级别的健名,默认为level
              TimeKey string `json:\”timeKey\” yaml:\”timeKey\”`//记录日志时间的健名,默认为time
              NameKey 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 Level
              Time time.Time
              LoggerName string
              Message string
              Caller EntryCaller
              Stack string
              }

              例子1:基本配置

              zap.Config 自定义配置,看官方的一个基本例子:

              package main

              import (
              \”encoding/json\”

              \”go.uber.org/zap\”
              )

              // https://pkg.go.dev/go.uber.org/zap@v1.24.0#hdr-Configuring_Zap
              func main() {
              // 表示 zap.Config 的 json 原始编码
              // outputPath: 设置日志输出路径,日志内容输出到标准输出和文件 logs.log
              // errorOutputPaths:设置错误日志输出路径
              rawJSON := []byte(`{
              \”level\”: \”debug\”,
              \”encoding\”: \”json\”,
              \”outputPaths\”: [\”stdout\”, \”./logs.log\”],
              \”errorOutputPaths\”: [\”stderr\”],
              \”initialFields\”: {\”foo\”: \”bar\”},
              \”encoderConfig\”: {
              \”messageKey\”: \”message-customer\”,
              \”levelKey\”: \”level\”,
              \”levelEncoder\”: \”lowercase\”
              }
              }`)

              // 把 json 格式数据解析到 zap.Config struct
              var cfg zap.Config
              if err := json.Unmarshal(rawJSON, &cfg); err != nil {
              panic(err)
              }
              // cfg.Build() 为配置对象创建一个 Logger
              // zap.Must() 封装了 Logger,Must()函数如果返回值不是 nil,就会报 panic。也就是检查Build是否错误
              logger := zap.Must(cfg.Build())
              defer logger.Sync()

              logger.Info(\”logger construction succeeded\”)
              }

              /*
              Must() 函数
              // var logger = zap.Must(zap.NewProduction())
              func Must(logger *Logger, err error) *Logger {
              if err != nil {
              panic(err)
              }

              return logger
              }
              */

              consol 输出如下:

              {"level":"info","message-customer":"logger construction succeeded","foo":"bar"}

              并且在程序目录下生成了一个文件 logs.log,里面记录的日志内容也是上面consol输出内容。每运行一次就在日志文件末尾append一次内容。

              例子2:高级配置

              上面的配置只是基本的自定义配置,如果有一些复杂的需求,比如在多个文件之间分割日志。

              或者输出到不是 file 的文件中,比如输出到 kafka 中,那么就需要使用 zapcore 包。

              在下面的例子中,我们将把日志输出到 kafka 中,并且也输出到 console 里。并且我们对 kafka 不同主题进行编码设置,对输出到 console 编码进行设置,也希望处理高优先级的日志。

              官方例子:

              package main

              import (
              \”io\”
              \”os\”

              \”go.uber.org/zap\”
              \”go.uber.org/zap/zapcore\”
              )

              func main() {
              // 首先,定义不同级别日志处理逻辑
              highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
              return lvl >= zapcore.ErrorLevel
              })
              lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
              return lvl < zapcore.ErrorLevel
              })

              // 假设有2个kafka 的 topic,一个 debugging,一个 errors

              // zapcore.AddSync 添加一个文件句柄。
              topicDebugging := zapcore.AddSync(io.Discard)
              topicErrors := zapcore.AddSync(io.Discard)

              // 如果他们对并发使用不安全,我们可以用 zapcore.Lock 添加一个 mutex 互斥锁。
              consoleDebugging := zapcore.Lock(os.Stdout)
              consoleErrors := zapcore.Lock(os.Stderr)

              // 设置 kafka 和 console 输出配置
              kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
              consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())

              // 把上面的设置加入到 zapcore.NewCore() 函数里,然后再把他们加入到 zapcore.NewTee() 函数里
              core := zapcore.NewTee(
              zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
              zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
              zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
              zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
              )

              // 最后调用 zap.New() 函数
              logger := zap.New(core)
              defer logger.Sync()
              logger.Info(\”constructed a logger\”)
              }

              例子3:日志写入文件

              与上面例子2相似,但是比它简单

              package main

              import (
              \”os\”

              \”go.uber.org/zap\”
              \”go.uber.org/zap/zapcore\”
              )

              func main() {
              writetofile()
              }

              func writetofile() {
              // 设置一些配置参数
              config := zap.NewProductionEncoderConfig()
              config.EncodeTime = zapcore.ISO8601TimeEncoder
              fileEncoder := zapcore.NewJSONEncoder(config)
              defaultLogLevel := zapcore.DebugLevel // 设置 loglevel

              logFile, _ := os.OpenFile(\”./log-test-zap.json\”, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 06666)
              // or os.Create()
              writer := zapcore.AddSync(logFile)

              logger := zap.New(
              zapcore.NewCore(fileEncoder, writer, defaultLogLevel),
              zap.AddCaller(),
              zap.AddStacktrace(zapcore.ErrorLevel),
              )
              defer logger.Sync()

              url := \”http://www.test.com\”
              logger.Info(\”write log to file\”,
              zap.String(\”url\”, url),
              zap.Int(\”attemp\”, 3),
              )
              }

              例子4:根据日志级别写入不同文件

              这个与上面例子2相似

              package main

              import (
              \”os\”

              \”go.uber.org/zap\”
              \”go.uber.org/zap/zapcore\”
              )

              func main() {
              writeToFileWithLogLevel()
              }

              func writeToFileWithLogLevel() {
              // 设置配置
              config := zap.NewProductionEncoderConfig()
              config.EncodeTime = zapcore.ISO8601TimeEncoder
              fileEncoder := zapcore.NewJSONEncoder(config)

              logFile, _ := os.OpenFile(\”./log-debug-zap.json\”, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //日志记录debug信息

              errFile, _ := os.OpenFile(\”./log-err-zap.json\”, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //日志记录error信息

              teecore := zapcore.NewTee(
              zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.DebugLevel),
              zapcore.NewCore(fileEncoder, zapcore.AddSync(errFile), zap.ErrorLevel),
              )

              logger := zap.New(teecore, zap.AddCaller())
              defer logger.Sync()

              url := \”http://www.diff-log-level.com\”
              logger.Info(\”write log to file\”,
              zap.String(\”url\”, url),
              zap.Int(\”time\”, 3),
              )

              logger.With(
              zap.String(\”url\”, url),
              zap.String(\”name\”, \”jimmmyr\”),
              ).Error(\”test error \”)
              }

              主要是设置日志级别,和把 2 个设置的 NewCore 放入到方法 NewTee 中。

              六、Hook和Namespace

              zap.Hook():

              Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数。

              package main

              import (
              \”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\”)
              }

              zap.Namespace():

              创建一个命名空间,后面的字段都在这名字空间中。Namespace 就像一个文件夹,后面文件都放在这个文件夹里。

              package main

              import (
              \”go.uber.org/zap\”
              )

              func main() {
              logger := zap.NewExample()
              defer logger.Sync()

              logger.Info(\”some message\”,
              zap.Namespace(\”shop\”),
              zap.String(\”name\”, \”LiLei\”),
              zap.String(\”grade\”, \”No2\”),
              )

              logger.Error(\”some error message\”,
              zap.Namespace(\”shop\”),
              zap.String(\”name\”, \”LiLei\”),
              zap.String(\”grade\”, \”No3\”),
              )
              }

              输出:

              {"level":"info","msg":"some message","shop":{"name":"LiLei","grade":"No2"}} {"level":"error","msg":"some error message","shop":{"name":"LiLei","grade":"No3"}}

              七、日志切割归档

              lumberjack这个库是按照日志大小切割日志文件。

              安装 v2 版本:

              go get -u github.com/natefinch/lumberjack@v2

              Code:

              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 main

              import (
              \”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, // megabytes
              MaxBackups: 3,
              MaxAge: 28, //days
              Compress: 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 \”)
              }

              }

              八、其它方法使用

              全局 Logger

              zap提供了 2 种全局 Logger,一个是 zap.Logger,调用 zap.L() 获取;

              另外一个是 zap.SugaredLogger ,调用 zap.S() 获取。

              注意:直接调用 zap.L() 或 zap.S() 记录日志的话,它是不会记录任何日志信息。需要调用 ReplaceGlobals() 函数将它设置为全局 Logger。

              ReplaceGlobals 替换全局 Logger 和 SugaredLogger,并返回一个函数来恢复原始值。

              并发使用它是安全的。

              看看zap/global.go中的源码:

              // https://github.com/uber-go/zap/blob/v1.24.0/global.go

              var (
              _globalMu sync.RWMutex
              _globalL = NewNop()
              _globalS = _globalL.Sugar()
              )

              func L() *Logger {
              _globalMu.RLock() // 加了读锁,所以并发使用是安全的
              l := _globalL
              _globalMu.RUnlock()
              return l
              }

              func S() *SugaredLogger {
              _globalMu.RLock() // 加了读锁,所以并发使用是安全的
              s := _globalS
              _globalMu.RUnlock()
              return s
              }

              func ReplaceGlobals(logger *Logger) func() {
              _globalMu.Lock()
              prev := _globalL
              _globalL = logger
              _globalS = logger.Sugar()
              _globalMu.Unlock()
              return func() { ReplaceGlobals(prev) } // 返回一个函数类型
              }

              上面源码中的关键是 _globalL = NewNop() , NewNop 函数源码在 zap/logger.go 中,这个函数返回初始化了的一个 *Logger:

              // https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L85

              func NewNop() *Logger {
              return &Logger{
              core: zapcore.NewNopCore(),
              errorOutput: zapcore.AddSync(io.Discard),
              addStack: zapcore.FatalLevel + 1,
              clock: zapcore.DefaultClock,
              }
              }

              上面是源码简析,下面给出一个简单使用的例子。

              简单使用例子:

              package main

              import (
              \”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]\”)
              }

              运行输出:

              {"level":"info","msg":"log info"} {"level":"info","msg":"log info [sugared]"}

              与标准日志库搭配

              zap 提供了一个函数NewStdLog,可以把标准日志库 log 转换为 zap 的日志,这为我们从标准日志库转换到 zap 日志库的使用提供了简洁的转换操作。

              例子:

              package main

              import (
              \”go.uber.org/zap\”
              )

              func main() {
              logger := zap.NewExample()
              defer logger.Sync()

              std := zap.NewStdLog(logger)
              std.Print(\”standard logger wrapper\”)
              }

              运行输出:

              {"level":"info","msg":"standard logger wrapper"}

              如果你还想设置日志级别,可以使用另外一个函数NewStdLogAt,它的第二个参数就是日志级别:

              NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error)

              一段代码中使用log另外的使用zap

              zap 还提供了另外一个函数RedirectStdLog,它可以帮助我们在一段代码中使用标准日志库 log,其它地方还是使用zap.Logger。如下例子:

              package main

              import (
              \”log\”

              \”go.uber.org/zap\”
              )

              func main() {
              logger := zap.NewExample()
              defer logger.Sync()

              undo := zap.RedirectStdLog(logger)
              log.Print(\”redirected standard library\”)
              undo()

              log.Print(\”this zap logger\”)
              }

              输出:

              {"level":"info","msg":"redirected standard library"} 2023/05/06 00:47:11 this zap logger

              同样如果想增加日志级别,可以使用函数RedirectStdLogAt:

              func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error)

              输出调用堆栈

              主要是调用函数zap.AddStacktrace(),见下面例子:

              package main

              import (
              \”go.uber.org/zap\”
              \”go.uber.org/zap/zapcore\”
              )

              func Hello() {
              Warn(\”hello\”, zap.String(\”h\”, \”world\”), zap.Int(\”c\”, 1))
              }

              func Warn(msg string, fields …zap.Field) {
              zap.L().Warn(msg, fields…)
              }

              func main() {
              logger, _ := zap.NewProduction(zap.AddStacktrace(zapcore.WarnLevel))
              defer logger.Sync()

              zap.ReplaceGlobals(logger)

              Hello()
              }

              运行输出:

              {"level":"warn","ts":1683306442.3578277,"caller":"zapdemos/addstacktrace.go:13","msg":"hello","h":"world","c":1, "stacktrace": "main.Warn\\n\\tD:/work/mygo/go-exercises/zapdemos/addstacktrace.go:13\\n main.Hello\\n\\tD:/work/mygo/go-exercises/zapdemos/addstacktrace.go:9\\n main.main\\n\\tD:/work/mygo/go-exercises/zapdemos/addstacktrace.go:22\\n runtime.main\\n\\tE:/programfile/go/src/runtime/proc.go:250"}

              输出文件名和行号

              AddCaller 将 Logger 配置为使用 zap 调用者的文件名、行号和函数名称,把这些信息添加到日志记录中。它底层调用的是WithCaller。

              addcaller.go:

              package main

              import (
              \”go.uber.org/zap\”
              )

              func main() {
              logger, _ := zap.NewProduction(zap.AddCaller())
              defer logger.Sync()

              logger.Info(\”AddCaller:line No and filename\”)
              }

              输出:

              {"level":"info","ts":1683307204.6184027,"caller":"zapdemos/addcaller.go:11","msg":"AddCaller:line No and filename"}

              logger.Info() 方法在第11行被调用。

              zap 还提供了另外一个函数zap.AddCallerSkip(skip int) Option,可以设置向上跳几层,然后记录文件名和行号。向上跳几层就是跳过调用者的数量。有时函数调用可能有嵌套,用这个函数可以定位到里面的函数。

              addcallerskip.go

              package main

              import (
              \”go.uber.org/zap\”
              )

              func main() {
              logger, _ := zap.NewProduction(zap.AddCaller(), zap.AddCallerSkip(1))
              defer logger.Sync()

              zap.ReplaceGlobals(logger)

              Hello()
              }

              func Hello() {
              Warn(\”hello\”, zap.String(\”h\”, \”world\”), zap.Int(\”c\”, 1))
              }

              func Warn(msg string, fields …zap.Field) {
              zap.L().Warn(msg, fields…)
              }

              输出:

              {"level":"warn","ts":1683308118.1684704,"caller":"zapdemos/addcallerskip.go:17","msg":"hello","h":"world","c":1}

              日志中的 17 表示 Hello() 函数里的 Warn() 的行号。

              如果zap.AddCallerSkip(2),日志中显示行号为 13,表示 Hello() 的行号。

              九、zap使用总结

              • zap 的使用,先创建 logger,再调用各个日志级别方法记录日志信息。比如 logger.Info()。
              • zap 提供了三种快速创建 logger 的方法:zap.Newproduction(),zap.NewDevelopment(),zap.NewExample()。见名思义,Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中。这三种方法都预先设置好了配置信息。它们的日志数据类型输出都是强类型。
              • 当然,zap 也提供了给用户自定义的方法zap.New()。比如用户可以自定义一些配置信息等。
              • 在上面的例子中,几乎都有defer logger.Sync()这段代码,为什么?因为 zap 底层 API 允许缓冲日志以提高性能,在默认情况下,日志记录器是没有缓冲的。但是在进程退出之前调用Sync()方法是一个好习惯。
              • 如果你在 zap 中使用了sugaredlogger,把 zap 创建 logger 的三种方法用logger.Sugar()包装下,那么 zap 就支持 printf 风格的格式化输出,也支持以 w 结尾的方法。如 Infow,Infof 等。这种就是通用类型日志输出,不是强类型输出,不需要强制指定输出的数据类型。它们的性能区别,通用类型会比强类型下降 50% 左右。

              比如 Infow 的输出形式,Infow 不需要 zap.String 这种指定字段的数据类型。如下代码:

              sugar := logger.Sugar()
              sugar.Infow(\”failed to fetch URL\”,
              \”url\”, url,
              \”attempt\”, 3,
              \”backoff\”, time.Second,
              )

              强类型输出,比如 Info 方法输出字段和值就需要指定数据类型:

              logger.Info(\”failed to fetch url\”,
              // 强类型字段
              zap.String(\”url\”, \”http://example.com\”),
              zap.Int(\”attempt\”, 3),
              zap.Duration(\”backoff\”, time.Second),
              )

              强类型输出和通用类型输出区别

              通用类型输出,经过 interface{} 转换会有性能损失,标准库的 fmt.Printf 为了通用性就用了 interface{} 这种”万能型“的数据类型,另外它还使用了反射,性能进一步降低。

              zap 强类型输出,zap 为了提供日志输出性能,zap 的强类型输出没有使用 interface{} 和反射。zap 默认输出就是强类型。

              上面介绍,zap 中 3 种创建 logger 方式(zap.Newproduction(),zap.NewDevelopment(),zap.NewExample())就是强类型日志字段,当然,也可以转化为通用类型,用logger.Sugar()方法创建 SugaredLogger。

              zap.Namespace()创建一个命名空间,后面的字段都在这名字空间中。Namespace 就像一个文件夹,后面文件都放在这个文件夹里。

              logger.Info(\”some message\”,
              zap.Namespace(\”shop\”),
              zap.String(\”shopid\”, \”s1234323\”),
              )

              {"level":"info","msg":"some message","shop":{"shopid":"s1234323"}}

              十、Demo源码地址

              zap demos

              https://github.com/jiujuan/go-exercises/tree/main/zapdemos

              以上就是Golang日志操作库zap的使用详解的详细内容,更多关于Go日志操作库zap的资料请关注悠久资源网其它相关文章!

              您可能感兴趣的文章:

              • 详解如何在Go中使用Zap管理日志
              • GoLang基于zap日志库的封装过程详解
              • Go每日一库之zap日志库的安装使用指南
              • Golang定制化zap日志库使用过程分析
              • Go语言Zap日志库使用教程
              • ubergozap日志框架支持异步日志输出
              • Go学习笔记之Zap日志的使用

              收藏 (0) 打赏

              感谢您的支持,我会继续努力的!

              打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
              点赞 (0)

              悠久资源 Golang Golang日志操作库zap的使用详解 https://www.u-9.cn/jiaoben/golang/187150.html

              常见问题

              相关文章

              发表评论
              暂无评论
              官方客服团队

              为您解决烦忧 - 24小时在线 专业服务