天天看點

golang 日志庫--Zap

作者:寒笛過霜天

github位址:

https://github.com/uber-go/zap

Star: 11.6k

文檔:

https://godoc.org/go.uber.org/zap

go get -u go.uber.org/zap

zap 提供了兩種類型的日志記錄器 Logger 和 Sugared Logger。兩者之間的差別是:

在每一微秒和每一次記憶體配置設定都很重要的上下文中, 使用Logger。它甚至比SugaredLogger更快, 記憶體配置設定次數也更少, 但它隻支援強類型的結構化日志記錄。

在性能很好但不是很關鍵的上下文中, 使用SugaredLogger。它比其他結構化日志記錄包快 4-10 倍, 并且支援結構化和 printf 風格的日志記錄。

是以一般場景下我們使用 Sugared Logger 就足夠了。

1 Logger

通過調用zap.NewProduction()/zap.NewDevelopment()或者zap.NewExample()建立一個 Logger 。

上面的每一個函數都将建立一個 logger, 唯一的差別在于它将記錄的資訊不同, 例如 production logger 預設記錄調用函數資訊、日期和時間等。

通過 Logger 調用 INFO 、 ERROR 等。

預設情況下日志都會列印到應用程式的 console 界面。

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
goroutine 1 [running]:
......
*/           

1.2 NewDevelopment

package main
import (
"go.uber.org/zap"
)
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
*/           

1.3 NewProduction

package main
import (
"go.uber.org/zap"
)
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":1610459247.392389,"caller":"test/log.go:10","msg":"this is info message"}
{"level":"info","ts":1610459247.392389,"caller":"test/log.go:11","msg":"this is info message with fileds","age":24,"agender":"man"}
{"level":"warn","ts":1610459247.3933866,"caller":"test/log.go:13","msg":"this is warn message"}
{"level":"error","ts":1610459247.3943844,"caller":"test/log.go:14","msg":"this is error message","stacktrace":"main.main\n\tD:/test/log.go:14\nruntime.main\n\tc:/go/src/runtime/proc.go:203"}
*/           

1.4 對比總結

Example和Production使用的是 json 格式輸出,Development 使用行的形式輸出

Development

從警告級别向上列印到堆棧中來跟蹤

始終列印包/檔案/行(方法)

在行尾添加任何額外字段作為 json 字元串

以大寫形式列印級别名稱

以毫秒為機關列印 ISO8601 格式的時間戳

Production

調試級别消息不記錄

Error, Dpanic 級别的記錄, 會在堆棧中跟蹤檔案, Warn 不會

始終将調用者添加到檔案中

以時間戳格式列印日期

以小寫形式列印級别名稱

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格式記錄語句:

package main
import (
"go.uber.org/zap"
)
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}
*/           

3 将日志寫入檔案

我們将使用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")
}           

輸出日志檔案:

2021-01-12T22:10:54.905+0800 INFO test/log.go:40 this is info message

2021-01-12T22:10:54.917+0800 INFO test/log.go:41 this is aaa, 1234

2021-01-12T22:10:54.917+0800 ERROR test/log.go:42 this is error message

2021-01-12T22:10:54.917+0800 INFO test/log.go:43 this is info message

4 使用 lumberjack 進行日志切割歸檔

因為 zap 本身不支援切割歸檔日志檔案, 為了添加日志切割歸檔功能, 我們将使用第三方庫 lumberjack 來實作。

4.1 安裝 lumberjack

執行下面的指令安裝 lumberjack

go get -uv github.com/natefinch/lumberjack

4.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")
}           

5 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