三個(gè)實(shí)用細(xì)節(jié),讓Zap在Go項(xiàng)目中變得更好用
一個(gè)項(xiàng)目日志功能夠不夠健全、記錄的日志內(nèi)容夠不夠有辨識(shí)度直接決定了一個(gè)項(xiàng)目維護(hù)的難度,你查日志是大海撈針一點(diǎn)點(diǎn)看,還是能夠靠一些有辨識(shí)度的索引篩選出用戶訪問程序期間留下的包含了完整上下文的日志直接決定了你搞明白“為什么會(huì)這樣”所耗費(fèi)時(shí)間的多少。
從本節(jié)開始我們先用兩節(jié)為我們的Go項(xiàng)目定制日志組件,讓它足夠好用。
未來我們會(huì)用這個(gè)組件一步步完善項(xiàng)目的應(yīng)用日志規(guī)范,讓項(xiàng)目框架能為我們把關(guān)鍵的上下文信息記錄到日志中,保證我們即使自己忘記打日志的情況下框架依然能為我們記錄下一些關(guān)鍵日志。
圖片
本節(jié)項(xiàng)目的所有源碼和測(cè)試接口都單獨(dú)封存了Git版本, 方便大家在自己機(jī)器上快速調(diào)試和學(xué)習(xí)。
圖片
安裝 Zap 和相關(guān)配置信息準(zhǔn)備
Zap是Uber開源的Go日志組件,它的優(yōu)勢(shì)什么的我就不過多介紹了,這兩節(jié)介紹的內(nèi)容更多地是關(guān)注怎么給自己的項(xiàng)目框架定制一個(gè)比較好用日志組件,其中介紹的方法思路換做其他的Go開源日志組件也同樣適用
我們首先來安裝一下 Zap ,這個(gè)時(shí)候可以打開你自己新建的項(xiàng)目來跟著操作
go get go.uber.org/zap@v1.21.0
把日志寫入文件,同時(shí)完成日志文件的切割歸檔需要借助另外一個(gè)開源庫 lumberjack,我們把它也安裝一下
go get gopkg.in/natefinch/lumberjack.v2@v2.0.0
安裝完成后我們先添加幾個(gè)與日志相關(guān)的配置,好能通過配置控制日志文件的路徑和文件大小等選項(xiàng)
打開項(xiàng)目開發(fā)環(huán)境的配置文件 config/application.dev.yaml, 我們?cè)赼pp原配置基礎(chǔ)上,加了log相關(guān)的三個(gè)配置。
app:
env: dev
name: go-mall
log:
path: "/tmp/applog/go-mall.log"
max_size: 100
max_age: 60
這里注意一下,開發(fā)環(huán)境日志文件放在/tmp目錄下主要是為了避免在電腦上很多目錄的權(quán)限限制比較嚴(yán)格程序沒辦法寫日志的問題。測(cè)試環(huán)境和生存環(huán)境的日志文件路徑建議設(shè)置成 /home/applog/go-mall/go-mall.log 這樣的路徑。
配置文件加好后,相應(yīng)的我們的配置對(duì)象也要根據(jù)新增配置進(jìn)行調(diào)整。
type appConfig struct {
Name string `mapstructure:"name"`
Env string `mapstructure:"env"`
Log struct {
FilePath string `mapstructure:"path"`
FileMaxSize int `mapstructure:"max_size"`
BackUpFileMaxAge int `mapstructure:"max_age"`
}
}
初始化日志組件
接下來我們先初始化Zap, 把它做為我們?nèi)罩窘M件的基礎(chǔ)Logger,配置完后我們會(huì)在其上封裝一個(gè)門面,讓Logger 變得更好用一些,通過這個(gè)門面除了能簡(jiǎn)化我們使用Zap打日志的操作方式外,還會(huì)給日志自動(dòng)追加一些追蹤和定位信息便于我們追蹤日志和定位程序問題,這個(gè)下個(gè)章節(jié)再講,本節(jié)先把基礎(chǔ)的東西做好。
接下里,先在項(xiàng)目中新建一個(gè) common 目錄
.
|-- common
| |-- enum
| |-- logger
|-- main.go
|-- go.mod
|-- go.sum
logger目錄中先新建 zap.go 在文件中對(duì)Zap進(jìn)行初始化相關(guān)的操作。
func init() {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoder := zapcore.NewJSONEncoder(encoderConfig)
fileWriteSyncer := getFileLogWriter()
var cores []zapcore.Core
......
core := zapcore.NewTee(cores...)
_logger = zap.New(core)
}
因?yàn)閆ap我們只會(huì)把它當(dāng)作基礎(chǔ)Logger,所以把它的變量定義成了只能在 logger 包內(nèi)訪問的全局變量
var _logger *zap.Logger
我們都知道針對(duì)不同的運(yùn)行環(huán)境,日志的最低級(jí)別不太一樣,比如說在開發(fā)環(huán)境中我們會(huì)打很多Debug日志,這個(gè)日志到生產(chǎn)環(huán)境上應(yīng)該被自動(dòng)過濾掉,如果不支持這個(gè)功能的話就得每次在代碼里把自己寫過的Debug日志的代碼行刪掉,這個(gè)相信誰都辦不到。
所以我們從底層Logger下手讓程序運(yùn)行在服務(wù)器上時(shí)不收集Debug日志。
var cores []zapcore.Core
switch config.App.Env {
case enum.ModeTest, enum.ModeProd:
// 測(cè)試環(huán)境和生產(chǎn)環(huán)境的日志輸出到文件中
cores = append(cores, zapcore.NewCore(encoder, fileWriteSyncer, zapcore.InfoLevel))
case enum.ModeDev:
// 開發(fā)環(huán)境同時(shí)向控制臺(tái)和文件輸出日志, Debug級(jí)別的日志也會(huì)被輸出
cores = append(
cores,
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel),
)
}
通過上面這幾行代碼的設(shè)置讓 Zap 在開發(fā)環(huán)境中可以寫Debug級(jí)別的日志,并且除了向文件里寫日志外,還同時(shí)向終端控制臺(tái)寫日志,這樣我們打的日志就能出現(xiàn)在程序運(yùn)行的控制臺(tái)中,方便我們快速Debug。
日志文件的管理
Zap沒有自動(dòng)管理和切割日志文件的功能,這個(gè)功能我們要借助 lumberjack 這個(gè)庫。
func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) {
// 使用 lumberjack 實(shí)現(xiàn) logger rotate
lumberJackLogger := &lumberjack.Logger{
Filename: config.App.Log.FilePath,
MaxSize: config.App.Log.FileMaxSize, // 文件最大 100 M
MaxAge: config.App.Log.BackUpFileMaxAge, // 舊文件最多保留90天
Compress: false,
LocalTime: true,
}
return zapcore.AddSync(lumberJackLogger)
}
創(chuàng)建 LumberJack 的 Logger 然后把它設(shè)置成 Zap 的 WriteSyncer ,這樣使用 Zap 打的日志就會(huì)寫到文件中
...
fileWriteSyncer := getFileLogWriter()
...
cores = append(cores, zapcore.NewCore(encoder, fileWriteSyncer, zapcore.InfoLevel))
創(chuàng)建LumberJack時(shí)可以定義日志文件的幾個(gè)選項(xiàng)
- FileName 日志的路徑,這個(gè)dev環(huán)境我們使用的是/tmp/applog/go-mall.log 。測(cè)試和生產(chǎn)環(huán)境建議設(shè)置成/home/applog/{項(xiàng)目}/{項(xiàng)目}.log,一來存放在/tmp中可能會(huì)被系統(tǒng)清理,二來通過固定的目錄可以讓ELK的日志收集組件去固定目錄抽取日志文件把日志收集到統(tǒng)一的日志平臺(tái)。
- MaxSize:?jiǎn)蝹€(gè)日志文件的最大尺寸,上面配置里定義的是100 對(duì)應(yīng)的尺寸是100M,日志文件達(dá)到這個(gè)大小后Lumber Jack會(huì)自動(dòng)切割日志文件,把原來的日志保存到備份文件中
- MaxAge:?jiǎn)挝皇翘欤O(shè)置60 就是備份文件最多保存60天
效果測(cè)試
日志文件Writer初始化并設(shè)置給Zap 后我們可以測(cè)試下是否有效果,我測(cè)試的時(shí)候是先把MaxSize 改成1 , 即最大1M,隨便寫個(gè)測(cè)試方法在里面寫日志,瘋狂刷了一會(huì)兒接口讓日志文件大小超過1M。