Go 学习笔记(54)— Go 第三方库之 uber-go/zap/lumberjack(记录日志到文件、支持自动分割日志、支持日志级别、打印调用文件、函数和行号)
1. 简要说明
zap
是 uber
开源的 Go
高性能日志库,支持不同的日志级别, 能够打印基本信息等,但不支持日志的分割,这里我们可以使用 lumberjack
也是 zap
官方推荐用于日志分割,结合这两个库我们就可以实现以下功能的日志机制:
- 能够将事件记录到文件中,而不是应用程序控制台;
- 日志切割能够根据文件大小、时间或间隔等来切割日志文件;
- 支持不同的日志级别,例如
DEBUG
,INFO
,WARN
,ERROR
等; - 能够打印基本信息,如调用文件、函数名和行号,日志时间等;
官网地址:https://github.com/uber-go/zap
2. 下载安装
使用下面命令安装
go get -u go.uber.org/zap
如果下载失败,则使用以下命令重新下载安装
go get github.com/uber-go/zap
下载安装成功后还有如下提示:
package github.com/uber-go/zap: code in directory
/home/wohu/GoCode/src/github.com/uber-go/zap expects import "go.uber.org/zap"
注意,不能通过 下面的语句导入该包,会有上面的错误提示
import ("github.com/uber-go/zap"
)
原因是作者开发它时的工程目录本来就是 go.uber.org/zap
,只是它的代码发布到 git
的目录是 github.com/uber-go/zap
而已。
解决方法是将 zap
目录复制到 GOPATH/src/go.uber.org
下(可能还会需要 go.uber.org/atomic
和 go.uber.org/multierr
,均可参考该方法 get
下来)。
go get -v github.com/uber-go/atomic
go get -v github.com/uber-go/multierr
同样将 atomic
和 multierr
拷贝到 go.uber.org
目录下。
3. 配置 zap Logger
zap
提供了两种类型的日志记录器—和 Logger
和 Sugared Logger
。两者之间的区别是:
- 在每一微秒和每一次内存分配都很重要的上下文中,使用
Logger
。它甚至比SugaredLogger
更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。 - 在性能很好但不是很关键的上下文中,使用
SugaredLogger
。它比其他结构化日志记录包快 4-10 倍,并且支持结构化和printf
风格的日志记录。
所以一般场景下我们使用 Sugared Logger
就足够了。
3.1 Logger
- 通过调用
zap.NewProduction()
/zap.NewDevelopment()
或者zap.NewExample()
创建一个Logger
。 - 上面的每一个函数都将创建一个
logger
。唯一的区别在于它将记录的信息不同。例如production logger
默认记录调用函数信息、日期和时间等。 - 通过
Logger
调用INFO
、ERROR
等。 - 默认情况下日志都会打印到应用程序的
console
界面。
3.1.1 NewExample
代码示例:
package mainimport ("go.uber.org/zap"
)func main() {log := zap.NewExample()log.Debug("this is debug message")log.Info("this is info message")log.Info("this is info message with fileds",zap.Int("age", 24), zap.String("agender", "man"))log.Warn("this is warn message")log.Error("this is error message")log.Panic("this is panic 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":24,"agender":"man"}
{"level":"warn","msg":"this is warn message"}
{"level":"error","msg":"this is error message"}
{"level":"panic","msg":"this is panic message"}
panic: this is panic message
3.1.2 NewDevelopment
代码示例:
func main() {log, _ := zap.NewDevelopment()log.Debug("this is debug message")log.Info("this is info message")log.Info("this is info message with fileds",zap.Int("age", 24), zap.String("agender", "man"))log.Warn("this is warn message")log.Error("this is error message") // log.DPanic("This is a DPANIC message") // log.Panic("this is panic message")// log.Fatal("This is a FATAL message")}
输出结果:
2020-06-12T18:51:11.457+0800 DEBUG task/main.go:9 this is debug message
2020-06-12T18:51:11.457+0800 INFO task/main.go:10 this is info message
2020-06-12T18:51:11.457+0800 INFO task/main.go:11 this is info message with fileds {"age": 24, "agender": "man"}
2020-06-12T18:51:11.457+0800 WARN task/main.go:13 this is warn message
main.main/home/wohu/GoCode/src/task/main.go:13
runtime.main/usr/local/go/src/runtime/proc.go:200
2020-06-12T18:51:11.457+0800 ERROR task/main.go:14 this is error message
main.main/home/wohu/GoCode/src/task/main.go:14
runtime.main/usr/local/go/src/runtime/proc.go:200
3.1.3 NewProduction
代码示例:
func main() {log, _ := zap.NewProduction()log.Debug("this is debug message")log.Info("this is info message")log.Info("this is info message with fileds",zap.Int("age", 24), zap.String("agender", "man"))log.Warn("this is warn message")log.Error("this is error message") // log.DPanic("This is a DPANIC message") // log.Panic("this is panic message")// log.Fatal("This is a FATAL message")
}
输出结果:
{"level":"info","ts":1591959367.316352,"caller":"task/main.go:10","msg":"this is info message"}
{"level":"info","ts":1591959367.3163702,"caller":"task/main.go:11","msg":"this is info message with fileds","age":24,"agender":"man"}
{"level":"warn","ts":1591959367.3163917,"caller":"task/main.go:13","msg":"this is warn message"}
{"level":"error","ts":1591959367.3163974,"caller":"task/main.go:14","msg":"this is error message","stacktrace":"main.main\n\t/home/wohu/GoCode/src/task/main.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:200"}
3.1.4 对比总结
Example
和Production
使用的是json
格式输出,Development
使用行的形式输出Development
- 从警告级别向上打印到堆栈中来跟踪
- 始终打印包/文件/行(方法)
- 在行尾添加任何额外字段作为
json
字符串 - 以大写形式打印级别名称
- 以毫秒为单位打印 ISO8601 格式的时间戳
Production
- 调试级别消息不记录
Error
,Dpanic
级别的记录,会在堆栈中跟踪文件,Warn
不会- 始终将调用者添加到文件中
- 以时间戳格式打印日期
- 以小写形式打印级别名称
在上面的代码中,我们首先创建了一个 Logger
,然后使用 Info
/ Error
等 Logger
方法记录消息。
日志记录器方法的语法是这样的:
func (log *Logger) MethodXXX(msg string, fields ...Field)
其中 MethodXXX
是一个可变参数函数,可以是 Info
/ Error
/ Debug
/ Panic
等。每个方法都接受一个消息字符串和任意数量的 zapcore.Field
长参数。
每个 zapcore.Field
其实就是一组键值对参数。
3.2 Sugared Logger
默认的 zap
记录器需要结构化标签,即对每个标签,需要使用特定值类型的函数。
log.Info("this is info message with fileds",zap.Int("age", 24), zap.String("agender", "man"))
虽然会显的很长,但是对性能要求较高的话,这是最快的选择。也可以使用suger logger
, 它基于 printf
分割的反射类型检测,提供更简单的语法来添加混合类型的标签。
我们使用 Sugared Logger
来实现相同的功能。
- 大部分的实现基本都相同;
- 惟一的区别是,我们通过调用主
logger
的.Sugar()
方法来获取一个SugaredLogger
; - 然后使用
SugaredLogger
以printf
格式记录语句;
func main() {logger, _ := zap.NewDevelopment()slogger := logger.Sugar()slogger.Debugf("debug message age is %d, agender is %s", 19, "man")slogger.Info("Info() uses sprint")slogger.Infof("Infof() uses %s", "sprintf")slogger.Infow("Infow() allows tags", "name", "Legolas", "type", 1)}
输出结果:
2020-06-12T19:23:54.184+0800 DEBUG task/main.go:11 debug message age is 19, agender is man
2020-06-12T19:23:54.185+0800 INFO task/main.go:12 Info() uses sprint
2020-06-12T19:23:54.185+0800 INFO task/main.go:13 Infof() uses sprintf
2020-06-12T19:23:54.185+0800 INFO task/main.go:14 Infow() allows tags {"name": "Legolas", "type": 1}
如果需要,可以随时使用记录器上的 .Desugar()
方法从 sugar logger
切换到标准记录器。
log := slogger.Desugar()log.Info("After Desugar; INFO message")
log.Warn("After Desugar; WARN message")
log.Error("After Desugar; ERROR message")
4. 将日志写入文件
我们将使用zap.New(…)
方法来手动传递所有配置,而不是使用像zap.NewProduction()
这样的预置方法来创建 logger
。
func New(core zapcore.Core, options ...Option) *Logger
zapcore.Core
需要三个配置——Encoder
,WriteSyncer
,LogLevel
。
Encoder
:编码器(如何写入日志)。我们将使用开箱即用的NewConsoleEncoder()
,并使用预先设置的ProductionEncoderConfig()
。
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
WriterSyncer
:指定日志将写到哪里去。我们使用zapcore.AddSync()
函数并且将打开的文件句柄传进去。
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)
Log Level
:哪种级别的日志将被写入。
代码示例:
package mainimport ("os""go.uber.org/zap""go.uber.org/zap/zapcore"
)var sugarLogger *zap.SugaredLoggerfunc InitLogger() {encoder := getEncoder()writeSyncer := getLogWriter()core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)// zap.AddCaller() 添加将调用函数信息记录到日志中的功能。logger := zap.New(core, zap.AddCaller())sugarLogger = logger.Sugar()
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间编码器// 在日志文件中使用大写字母记录日志级别encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder// NewConsoleEncoder 打印更符合人们观察的方式return zapcore.NewConsoleEncoder(encoderConfig)
}func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")return zapcore.AddSync(file)
}func main() {InitLogger()sugarLogger.Info("this is info message")sugarLogger.Infof("this is %s, %d", "aaa", 1234)sugarLogger.Error("this is error message")sugarLogger.Info("this is info message")
}
输出日志文件:
2020-06-16T09:01:06.192+0800 INFO task/main.go:40 this is info message
2020-06-16T09:01:06.192+0800 INFO task/main.go:41 this is aaa, 1234
2020-06-16T09:01:06.192+0800 ERROR task/main.go:42 this is error message
2020-06-16T09:01:06.192+0800 INFO task/main.go:43 this is info message
5. 使用 lumberjack 进行日志切割归档
因为 zap
本身不支持切割归档日志文件,为了添加日志切割归档功能,我们将使用第三方库 lumberjack
来实现。
5.1 安装 lumberjack
执行下面的命令安装 lumberjack
go get -uv github.com/natefinch/lumberjack
5.2 将 lumberjack 加入 zap logger
要在 zap
中加入 lumberjack
支持,我们需要修改 WriteSyncer
代码。我们将按照下面的代码修改 getLogWriter()
函数:
func getLogWriter() zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename: "./test.log",MaxSize: 10,MaxBackups: 5,MaxAge: 30,Compress: false,}return zapcore.AddSync(lumberJackLogger)
}
Lumberjack Logger
采用以下属性作为输入:
Filename
: 日志文件的位置;MaxSize
:在进行切割之前,日志文件的最大大小(以MB为单位);MaxBackups
:保留旧文件的最大个数;MaxAges
:保留旧文件的最大天数;Compress
:是否压缩/归档旧文件;
完整代码:
package mainimport ("github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore"
)var sugarLogger *zap.SugaredLoggerfunc InitLogger() {encoder := getEncoder()writeSyncer := getLogWriter()core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)// zap.AddCaller() 添加将调用函数信息记录到日志中的功能。logger := zap.New(core, zap.AddCaller())sugarLogger = logger.Sugar()
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间编码器// 在日志文件中使用大写字母记录日志级别encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder// NewConsoleEncoder 打印更符合人们观察的方式return zapcore.NewConsoleEncoder(encoderConfig)
}func getLogWriter() zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename: "./test.log",MaxSize: 10,MaxBackups: 5,MaxAge: 30,Compress: false,}return zapcore.AddSync(lumberJackLogger)
}func main() {InitLogger()sugarLogger.Info("this is info message")sugarLogger.Infof("this is %s, %d", "aaa", 1234)sugarLogger.Error("this is error message")sugarLogger.Info("this is info message")
}
6. Log 第三方库 uber-zap 使用
package main
import ("time""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore"
)
var logger *zap.Logger
// logpath 日志文件路径
// loglevel 日志级别
func InitLogger(logpath string, loglevel string) {// 日志分割hook := lumberjack.Logger{Filename: logpath, // 日志文件路径,默认 os.TempDir()MaxSize: 10, // 每个日志文件保存10M,默认 100MMaxBackups: 30, // 保留30个备份,默认不限MaxAge: 7, // 保留7天,默认不限Compress: true, // 是否压缩,默认不压缩}write := zapcore.AddSync(&hook)// 设置日志级别// debug 可以打印出 info debug warn// info 级别可以打印 warn info// warn 只能打印 warn// debug->info->warn->errorvar level zapcore.Levelswitch loglevel {case "debug":level = zap.DebugLevelcase "info":level = zap.InfoLevelcase "error":level = zap.ErrorLeveldefault:level = zap.InfoLevel}encoderConfig := zapcore.EncoderConfig{TimeKey: "time",LevelKey: "level",NameKey: "logger",CallerKey: "linenum",MessageKey: "msg",StacktraceKey: "stacktrace",LineEnding: zapcore.DefaultLineEnding,EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式EncodeDuration: zapcore.SecondsDurationEncoder, //EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器EncodeName: zapcore.FullNameEncoder,}// 设置日志级别atomicLevel := zap.NewAtomicLevel()atomicLevel.SetLevel(level)core := zapcore.NewCore(// zapcore.NewConsoleEncoder(encoderConfig),zapcore.NewJSONEncoder(encoderConfig),// zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&write)), // 打印到控制台和文件write,level,)// 开启开发模式,堆栈跟踪caller := zap.AddCaller()// 开启文件及行号development := zap.Development()// 设置初始化字段,如:添加一个服务器名称filed := zap.Fields(zap.String("serviceName", "serviceName"))// 构造日志logger = zap.New(core, caller, development, filed)logger.Info("DefaultLogger init success")
}
func main() {// 历史记录日志名字为:all.log,服务重新启动,日志会追加,不会删除InitLogger("./all.log", "debug")// 强结构形式logger.Info("test",zap.String("string", "string"),zap.Int("int", 3),zap.Duration("time", time.Second),)// 必须 key-value 结构形式 性能下降一点logger.Sugar().Infow("test-","string", "string","int", 1,"time", time.Second,)
}
从例子看出:
- 它同时提供了结构化日志记录和 printf 风格的日志记录
- 先初始化 lumberjack 后初始化 zap
7. 在何处打印日志
- 在分支语句处打印日志。在分支语句处打印日志,可以判断出代码走了哪个分支,有助于判断请求的下一跳,继而继续排查问题。
- 写操作必须打印日志。写操作最可能会引起比较严重的业务故障,写操作打印日志,可以在出问题时找到关键信息。
- 在循环中打印日志要慎重。如果循环次数过多,会导致打印大量的日志,严重拖累代码的性能,建议的办法是在循环中记录要点,在循环外面总结打印出来。
- 在错误产生的最原始位置打印日志。对于嵌套的 Error,可在 Error 产生的最初位置打印 Error 日志,上层如果不需要添加必要的信息,可以直接返回下层的 Error。
参考:
优秀开源日志包使用教程
https://studygolang.com/articles/19387
https://blog.csdn.net/niyuelin1990/article/details/78340336
https://blog.csdn.net/feifeixiang2835/article/details/94207810
https://blog.csdn.net/qq_27068845/article/details/103480451
https://blog.csdn.net/NUCEMLS/article/details/86534444
https://zhuanlan.zhihu.com/p/88856378
https://gitbook.cn/books/5e7637996ba17a6d2c9a3352/index.html
Go 学习笔记(54)— Go 第三方库之 uber-go/zap/lumberjack(记录日志到文件、支持自动分割日志、支持日志级别、打印调用文件、函数和行号)相关推荐
- python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑
python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件? 当我们点开下载页时, 一 ...
- python3第三方库手册_python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑...
python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件? 当我们点开下载页时, 一 ...
- python学习笔记项目_python第三方库之Django学习笔记一
1.安装Django pip install Django 2.版本号查询 python -m django --version 3.创建项目 切换到你想创建项目的目录,执行命令:django-adm ...
- STM32CUBEMX入门学习笔记3:HAL库以及STM32CUBE相关资料
STM32CUBEMX入门学习笔记3:HAL库以及STM32CUBE相关资料 微雪课堂:http://www.waveshare.net/study/article-629-1.html 之前的正点原 ...
- Makefile学习笔记07|编译静态库并通过ifeq语句
Makefile学习笔记07|编译静态库并通过ifeq语句 希望看到这篇文章的朋友能在评论区留下宝贵的建议来让我们共同成长,谢谢. 这里是目录 本篇与上一篇有较多联系,有兴趣的可以先看上一 ...
- seaJs学习笔记2 – seaJs组建库的使用
原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...
- Python学习笔记:常用第三方模块3
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python学习笔记:常用第三方模块(1)
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python3 爬虫学习笔记 C01 【基本库 urllib 的使用】
Python3 爬虫学习笔记第一章 -- [基本库 urllib 的使用] 文章目录 [1.1] urllib 简介 [1.2] urllib.request 发送请求 [1.2.1] urllib. ...
最新文章
- Sublime Text 3便携版下载安装和常用插件安装--顺便解决报错An error occured installing和no packages available for install
- 个人博客满血复活,求测试~~~
- python开发好学吗-Python的前景和Python好不好学呢?
- vue.js的一些事件绑定和表单数据双向绑定
- C++中四种强制类型转换的区别
- 时序分析:手势--空间轨迹模式识别
- nodejs 防宕机_pm2实战,让你的nodejs、koa2应用永远不会宕机
- 接口测试系列:工作中所用(十:配置文件的读写操作 configparser模块)
- POJ1039 Pipe
- 密码学基础(四):OpenSSL命令详解
- java web整合开发完全自学手册pdf_《JavaScript完全自学手册》PDF 下载
- 程序员必备的网站推荐
- 怎么在计算机中搜索可移动硬盘,为什么本地硬盘在计算机上显示为可移动硬盘...
- 读者福利,单独赠书啦!这次的书你肯定喜欢!
- Word2Vec与文章相似度
- js 京东关闭广告 pink
- 读书笔记:软件工程(1) - 软件工程概述(1)
- python 战舰_Python战舰:获取用户输入的他们想要多少艘战舰
- html中table整体缩小,html-如何缩小表格样式中的间隙?
- RAAT: Relation-Augmented Attention Transformer for Relation Modeling in Document-Level 论文解读