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 main
import (
"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 main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var sugarLogger *zap.SugaredLogger
func 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 main
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var sugarLogger *zap.SugaredLogger
func 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,默认 100M
MaxBackups: 30, // 保留30个备份,默认不限
MaxAge: 7, // 保留7天,默认不限
Compress: true, // 是否压缩,默认不压缩
}
write := zapcore.AddSync(&hook)
// 设置日志级别
// debug 可以打印出 info debug warn
// info 级别可以打印 warn info
// warn 只能打印 warn
// debug->info->warn->error
var level zapcore.Level
switch loglevel {
case "debug":
level = zap.DebugLevel
case "info":
level = zap.InfoLevel
case "error":
level = zap.ErrorLevel
default:
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