(一)介绍
-
zap 是go 中比较火的一个日志库,提供不同级别的日志,并且速度快
-
官方文档: https://pkg.go.dev/go.uber.org/zap#section-readme, 也可以github 直接搜索 zap。 文档上面有全面的介绍。鼓励大家观看文档, 可以有视频资料做相关引导,但学东西必须要看到官方文档。尤其是文档也已经适合入门了,先看Quick Start 部分,一般都有入门例子及整体框架介绍。
-
官方文档介绍,非常清晰,还有相关数据对比,并且一般都会具有 example文件夹提供相关的编程实例。
-
Quick Start(快速开始): zap提供了两种 日志记录器:
- (1)SugaredLogger:(加了糖的 Logger)
-
官方文档介绍: 在性能很好但不是很关键的环境中,使用SugaredLogger。它比其他结构化日志包快4-10倍,
并且包含结构化和printf风格的api
。// 创建一个 logger logger, _ := zap.NewProduction() defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘 sugar := logger.Sugar() sugar.Infow("failed to fetch URL", // 结构化上下文为松散类型的键值对。 "url", url, "attempt", 3, "backoff", time.Second, ) sugar.Infof("Failed to fetch URL: %s", url)
-
- (2)Logger
-
当性能和类型安全至关重要时,使用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), )
-
- 更多介绍可以看文档 choose a logger
-
在Logger和SugaredLogger之间进行选择不需要在应用程序范围内进行决定:在两者之间进行转换既简单又便宜。从上面就可以看出来,二者创建使用区别很小。
logger := zap.NewExample() defer logger.Sync() sugar := logger.Sugar() plain := sugar.Desugar()
-
- (1)SugaredLogger:(加了糖的 Logger)
(二)讲解 zap
1. 日志级别
- 在 const 文档下面,有介绍日志级别的定义
const ( // DebugLevel logs are typically voluminous, and are usually disabled in // production. DebugLevel = zapcore.DebugLevel // InfoLevel is the default logging priority. InfoLevel = zapcore.InfoLevel // WarnLevel logs are more important than Info, but don't need individual // human review. WarnLevel = zapcore.WarnLevel // ErrorLevel logs are high-priority. If an application is running smoothly, // it shouldn't generate any error-level logs. ErrorLevel = zapcore.ErrorLevel // DPanicLevel logs are particularly important errors. In development the // logger panics after writing the message. DPanicLevel = zapcore.DPanicLevel // PanicLevel logs a message, then panics. PanicLevel = zapcore.PanicLevel // FatalLevel logs a message, then calls os.Exit(1). FatalLevel = zapcore.FatalLevel )
2. 构建 looger
- 在文档的 Configuring Zap中:
构建Logger最简单的方法是使用zap固有的预设:NewExample、NewProduction和NewDevelopment。这些预置用一个函数调用构建一个日志记录器。 - 三者创建的 logger 是有区别的, 我们可以在官方文档的 type logger下面找到三个函数的介绍, 对应不同的场景。
-
func NewExample(options ...Option) *Logger
- NewExample构建了一个专门为zap的可测试示例设计的Logger。它将DebugLevel及以上的日志作为JSON写入标准输出,但省略了时间戳和调用函数,以保持示例输出的简短和确定性
-
func NewProduction(options ...Option) (*Logger, error)
- NewProduction构建了一个合理的生产日志记录器,它将infollevel及以上的日志以JSON的形式写入标准错误。
- 它是NewProductionConfig(). build(…Option)的快捷方式。
-
func NewDevelopment(options ...Option) (*Logger, error)
- NewDevelopment构建一个开发日志记录器,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。
- 这是NewDevelopmentConfig().Build(…选项)的快捷方式
- 就好比去商店卖商品,初上自带了几个配置好的模式。 通过配置生成对应的 logger。 我们也可以自定义 配置,生成自己自定义的 logger。
-
3. 方法使用
- 在文档的 types/logger 和 types/SaguredLogger 里面记录了相关的looger记录消息的使用方法。
-
以logger为例子:
-
都接受一个 msg String, 后面是可选的一些字段。Field 类型,可以查看文档有很多的类型。
-
写一个Get请求访问相应的网址,记录日志信息
package main import ( "net/http" "time" "go.uber.org/zap" ) func main() { // 创建一个 logger, 可以选择其他预置创建,会有不同的输出效果 logger, _ := zap.NewProduction() defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘 // 定义url url := "http://www.baidu.com" resp, err := http.Get(url) // 直接出错日志 if err != nil { logger.Error("访问失败了", zap.String("url", url), // 跟上错误信息 zap.Error(err), ) }else { // info 级别的日志信息打印 logger.Info("成功访问!", // 作为强类型字段值的结构化上下文. zap.String("url", url), zap.String("status", resp.Status), zap.Duration("backoff", time.Second), ) resp.Body.Close() } }
- 结果分别为:
{"level":"info","ts":1637632861.812557, "caller":"zap日志库学习/main.go:25", "msg":"成功访问!", "url":"http://www.baidu.com", "status":"200 OK","backoff":1} {"level":"error", "ts":1637633063.4520404, "caller":"zap日志库学习/main.go:18", "msg":"访问失败了", "url":"http://www.xxx.com", "error":"Get \"http://www.xxx.com\": dial tcp 69.171.228.20:80: connectex: A connection attempt failed because the connected party did not properly respond after a p eriod of time, or established connection failed because connected host has failed to respond.","stacktrace":"main.main\n\tD:/gofiles/go-learning -notes/go-learning/zap日志库学习/main.go:18\nruntime.main\n\tD:/software/go/go1.15/src/runtime/proc.go:204"}
- 可以看到执行程序后终端提示相关的信心。 msg 就是自己设置的,时间, url等。 还有一个 caller 调用者信息,指明问题出现的行数。
-
NewDevelopment() 创建的生成日志是这样的: 空格隔开。 缺少调用者
2021-11-23T10:08:26.171+0800 INFO zap日志库学习/main.go:25 成功访问! {"url": "http://www.baidu.com", "status": "200 OK", "backoff": "1s"}
-
NewExample()
{"level":"info","msg":"成功访问!","url":"http://www.baidu.com","status":"200 OK","backoff":"1s"}
-
4. 定制化 logger
-
查看NewProduction 的源码就能知道,实际底层就是:
NewProductionConfig().Build(options...)
-
调用了 NewProductionConfig()方法,内部初始化创建,返回了一个 Config 对象。
-
Build, 内部通过 Config对象的配置, 利用New方法生成相应的 logger对象,并返回。
-
也就是说,这是 zap库给我们预置的 NewProduction()等方法,内部是按照指定的配置,生成相应的 logger 日志对象。 我们也可以自己调用内部的相关方法, 模仿 NewProductionConfig().Build(options…) 相关过程,自己创建,定制化 logger对象。
-
观察New方法 生成logger 所需要的东西。在Build 函数中:
// 返回一个 Core对象, 需要的是 三个参数 func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core func New(core zapcore.Core, options ...Option) *Logger log := New( zapcore.NewCore(enc, sink, cfg.Level), cfg.buildOptions(errSink)..., )
-
官方对 Core的介绍 :需要打开对应的包,查看文档
Core是一个最小的、快速的记录器接口。它是为库作者设计的,用来封装更友好的API。
-
-
关于 NewProductionConfig()函数, 返回对应的Config 对象,Build 函数根据这个配置,进行生成 logger对象。
-
我们当然可以自定义这个, 来实现生成自己的logger. 下面看下它的源码
// NewProductionConfig是一个合理的生产日志配置。 //在infollevel及以上级别启用日志记录。 //它使用JSON编码器,写入标准错误,并启用采样。 // stacktrace会自动包含在ErrorLevel及以上的日志中。 func NewProductionConfig() Config { return Config{ // 日志级别 Level: NewAtomicLevelAt(InfoLevel), Development: false, Sampling: &SamplingConfig{ Initial: 100, Thereafter: 100, }, // 编码方式 Encoding: "json", // EncoderCofig, 配置 encoder 编辑器的默认配置。 EncoderConfig: NewProductionEncoderConfig(), // 打开的文件, 写入日志信息到这里。 OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } }
(1)如何写入日志文件
-
按照上面的自定义 logger, 创建核心Core需要三个参数,其中就有控制 写入文件的。
-
Encoder:
编辑器。提供了两种信息的编辑方式
。- 需要传递的参数,可以使用 默认的EncoderConfig: NewProductionEncoderConfig() 传递进去。
- 一种 文本样式的, 一种 json 样式的信息输入。
-
WriteSyncer:
指定日志写到哪里
。可以定义自己指定的文件路径-
通过func AddSync(w io.Writer) WriteSyncer 方法,返回一个。
AddSync用于转换io。Writer到WriteSyncer。它试图是智能的:如果具体类型的io。Writer实现了WriteSyncer,我们将使用现有的Sync方法。如果没有,我们将添加一个无操作同步。// 创建文件对象 file, _ := os.Create("./getLog.log") // 或者是用 OpenFile函数,在原来基础上追加。 // file, _ := os.OpenFile("./getLog.log", os.O_APPEND | os.O_RDWR, 0744) // 生成 WriteSyncer wSy := zapcore.AddSync(file)
-
-
LevelEnabler:
设置哪种级别的日志将被写入
- 对应的就是前面介绍的日志级别;如:
zapcore.DebugLevel
- 对应的就是前面介绍的日志级别;如:
-
创建自定义logger:
-
根据上面三点参数的理解,就可以指定文件建立了
// 还剩一个后面的配置信息没有传入,但是已经可以了 // 默认我们调用 NewProduction()方法也是没有传递啥配置进去的。 log := New( zapcore.NewCore(传递编辑器(两种), 自定义文件输出, cfg.Level(级别)), )
- 按照这样的建立完成之后就可以使用了,往指定文件里打印日志。
-
还有 zap 预置的生成 logger的方式,都是通过 NewProductionConfig() 来生成相关配置的, 也可以不用这么麻烦,我觉得直接, 自定义一个 NewProductionConfig() 然后,按着相应的步骤就可以了。 Build方法 就是通过 配置的 Config 对象,来生成的 logger。
-
这里有个系列教程文章:https://blog.csdn.net/weixin_39620252/article/details/111136566
-
像我这样:重写方法,只需要加个文件名就可以了
func myNewProduction(options ...zap.Option) (*zap.Logger, error) { return myNewProductionConfig().Build(options...) } func myNewProductionConfig() zap.Config { config := zap.NewProductionConfig() // 配置输出路径为 ./test.log 其他都不变, 内部默认是 json 编码。 只要 json 和 console两种。可以看 Encoding 字段的介绍。 config.OutputPaths = []string{"./test.log"} return config } // 创建logger 对象 logger, _ := myNewProduction()
-
(2)更改时间编码 及 添加调用者的详细信息
时间格式(或者说是编码)更改
-
时间显示这一块,我们可以看到,默认的 NewProductionConfig()函数里创建的字段 中的 对 Encoder 消息编辑器对象的配置 EncoderConfig
在文档中也能找到它的描述: https://pkg.go.dev/go.uber.org/zap@v1.19.1/zapcore#EncoderConfigfunc NewProductionEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ // 包含key的全部都是定义的输出字段名字。 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, }
-
我们顺着文档去找或者是 直接点进去 EncodeTime: zapcore.EpochTimeEncoder。 EncodeTime 顾名思义: 时间编码。就是这里控制的,点进去后,该函数下方还有好几个设置时间编码的函数, 文档中也能找到。
-
那么我们可以直接修改 对应的配置, 满足我们的自定义要求。
-
选择 ISO8601TimeEncoder: “2006-01-02T15:04:05.000Z0700” 比较容易看懂的格式。
-
按照上面修改导入文件的方法:
func myNewProductionConfig() zap.Config { config := zap.NewProductionConfig() // 修改导入文件路径 config.OutputPaths = []string{"./test.log"} // 修改时间格式: config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder return config }
- 再次运行,时间编码就会改变
{"level":"info","ts":"2021-11-23T17:13:39.430+0800","caller":"zap日志库学习/main.go:43","msg":"成功访问!","url":"http://www.baidu.com","status":"200 OK","backoff":1}
-
我们这里的写法,还是不断的 修改 NewProductionConfig() 为我们预置的配置。 如果想大改,创建自己的话, 就一步步创建 Core 核心。 然后 重新定义自己的 NewProductionEncoderConfig()。
-
显示详细的 添加调用者的详细信息。
-
根据时间的理解, 这里很容易想到: 配置项当中的 EncodeCaller, 也可以指定相关的函数,用来打印 调用者的信息。
-
还有一种方式是: 我们创建Core, zap.New()创建 logger, 分析第二个参数的 Options 类型, 文档中可以找到相关的方法,就有添加调试 显示调用人信息的方法。
func New(core zapcore.Core, options ...Option) *Logger log := zap.New( zapcore.NewCore(enc, sink, cfg.Level), zap.AddCaller(), )
5. Lumberjack 进行日志文件的切割归档
-
这部分就比较难了,没有实际操作过,就不懂。 参考文章或者相关视频,二者结合使用。
-
日志存的多的话, 日志文件越来越大, 几个G的话,打开和操作就太麻烦了! 那么就需要对日志文件进行分割,归档。
-
需要安装第三方库 :Lumberjack: go get -u github.com/natefinch/lumberjack
-
使用时仍然是打开文件, 配合 zap使用,需要创建新的 zapcore.WriteSyncer。
-
lumberjack.Logger 实现了 io.writer 接口,可以作为参数。
func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./test.log", // 导入文件名 MaxSize: 10, // 大小M兆 MaxBackups: 5, // 最大备份数量 MaxAge: 30, // 最大备份天数 Compress: false, // 是否压缩 } return zapcore.AddSync(lumberJackLogger) }
-
进行测试:
// 创建新的logger对象 encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) writeSyncer := getLogWriter() // 创建核心 newCore := zapcore.NewCore(encoder, writeSyncer, zapcore.InfoLevel) // 创建logger logger := zap.New(newCore) // 插入日志 for i := 0; i < 10000; i++ { logger.Info("成功访问!", // 作为强类型字段值的结构化上下文. zap.String("url", "测试归档"), zap.String("status", "添加数据"), zap.Duration("backoff", time.Second), ) } defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
- 效果展示:以当前时间戳建立新文件
- 归档文件中有八万多数据
- test.log 中只有: 3000多条
- 效果展示:以当前时间戳建立新文件
6. gin框架中配置zap记录日志
- 在上一篇文章中: 《详细讲解go web框架之gin框架源码解析记录及思路流程和理解》
- 讲解了 gin.Default 创建引擎, 默认的添加了两个中间件。 一个是 logger 日志,一个是 recover 恢复。 gin 自带的 logger 就是在这里实现起作用的。
- 那么我们也需要将 zap封装为 logger 中间件(HandlerFunc)。
- 具体实现可以参考 给出的两个 logger recover 中间件的实现,加以修改。
- 生成全局的 logger对象, 然后项目中可以调用使用,还可以加入中间件。 这里先不做演示了, 博客太长,电脑就卡死机了,,,,太卡了
- 讲解了 gin.Default 创建引擎, 默认的添加了两个中间件。 一个是 logger 日志,一个是 recover 恢复。 gin 自带的 logger 就是在这里实现起作用的。