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(记录日志到文件、支持自动分割日志、支持日志级别、打印调用文件、函数和行号)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 东方人物立绘是谁画的啊?
- 下一篇: libopencv_core.so.2.