golang 使用 Zap 日誌庫
- 簡要說明
zap
是 uber
開源的 Go
高性能日誌庫,支持不同的日誌級別, 能夠打印基本信息等,但不支持日誌的分割,這裏我們可以使用 lumberjack
也是 zap
官方推薦用於日誌分割,結合這兩個庫我們就可以實現以下功能的日誌機制:
-
能夠將事件記錄到文件中,而不是應用程序控制臺;
-
日誌切割能夠根據文件大小、時間或間隔等來切割日誌文件;
-
支持不同的日誌級別,例如
DEBUG
,INFO
,WARN
,ERROR
等; -
能夠打印基本信息,如調用文件、函數名和行號,日誌時間等;
官網地址:https://github.com/uber-go/zap
- 下載安裝
使用下面命令安裝
go get github.com/uber-go/zap
- 配置 zap Logger
zap
提供了兩種類型的日誌記錄器Logger
和Sugared Logger
區別是:
-
在每一微秒和每一次內存分配都很重要的上下文中,使用 Logger。它比 Sugared Logger 更快,內存分配次數也更少,但它只支持強類型的結構化日誌記錄。
-
在性能很好但不是很關鍵的上下文中,使用
SugaredLogger
。它比其他結構化日誌記錄包快 4-10 被,並且支持結構化和printf
風格的日誌記錄。
3.1 Logger
-
通過調用
zap.NewProduction()
/zap.NewDevelopment()
或者zap.NewExample()
創建一個 Logger。 -
上面的每一個函數都將創建一個
Logger
。唯一的區別在於它將記錄的信息不同。例如:production logger
默認記錄調用函數信息、日期和時間等。 -
通過
Logger
調用INFO
、ERROR
等
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 fields",
zap.Int("age",24),zap.String("agender","man"))
雖然會先的很長,但是對性能要求較高的話,這是最快的選擇。也可以使用Sugared 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")
- 將日誌寫入文件
我們將使用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())
writeSyncer
: 指定日誌將寫到那裏去。我們使用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
- 使用 lumberjack 進行日誌切割歸檔
因爲zap
本身不支持切割歸檔日誌文件,爲了添加日誌切割歸檔功能,我們將使用第三方庫lumberjack
來實現
5.1 安裝 lumberjack
go get 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")
}
- 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
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://zhuanlan.zhihu.com/p/371547318