位元組開源RPC框架Kitex的日誌庫klog源碼解讀
前言
這篇文章將着重於分析位元組跳動開源的RPC
框架Kitex的日誌庫klog
的源碼,通過對比Go原生日誌庫log
的實現,探究其作出的改進。
為了平滑學習曲線,我寫下了這篇分析Go原生log
庫的文章,希望你可以對比閱讀://juejin.cn/post/7103790667595268126
本文的分析基於:github.com/cloudwego/kitex/pkg/klog
的源碼。
klog庫的使用
結果如下:
klog.xxx能直接打印日誌的原因
通過觀察源碼,klog
包的default.go
文件中,封裝了三類日誌的打印的函數提供直接使用:普通日誌、格式化的日誌、格式化的Context日誌。
每一類包含了7個的日誌輸出級別的函數可使用:Info
、Debug
、Notice
、Warn
、Error
、Fatal
、Trace
。
並且這21個函數中頻繁使用到了一個logger
實例,只要我們引入klog
包,logger
就會完成初始化,並且作為默認的log
實例。
可以看到logger
實例默認的日誌打印級別是LevelInfo
,klog
通過常量計數器定義了0~6
種日誌級別:
FullLogger接口
默認的logger
實例是通過defaultLogger
結構初始化的,且defaultLogger
結構實現了FullLogger
接口定義的所有函數(接口定義了上面說的三類,每一類7種日誌打印函數)
並且觀察defaultLogger
結構的屬性stdlog
,就是Go原生的日誌庫log
定義的Logger
類型,因此klog
的所有日誌操作,最終都是藉助Go原生log
庫實現的。
相當於klog
在Go原生log庫
的基礎上對格式化輸出和日誌打印級別作了封裝,便於直接使用。
串聯一下日誌打印函數執行的過程:
main函數
中調用:klog.Info("一條普通的日誌")
- 進一步調用初始化好的
defaultLogger
實例(名為logger
)的實現自FullLogger
接口的函數:logger.Info()
- 進一步調用
ll.logf()
函數(下面重點分析)
ll.logf()
上面的這三類共21個日誌打印函數最終都調用了ll.logf()
方法,因此ll.logf()
也是klog
庫的核心函數,看一下代碼:
- 日誌過濾:如果調用的打印函數代表的日誌級別低於
logger
實例初始化的日誌級別,則不會打印(如默認級別是LevelInfo == 2
,則調用klog.Trace()
將被過濾) - 格式化打印信息存入
msg
- 調用Go原生日誌庫
log
的Output()
函數,打印日誌(這一部分在上一篇分析Go的log庫的文章中已經充分講解)
關於calldepth的問題
calldepth
表示調用層數,這裡聲明了4,是為了配合獲取調用日誌打印函數的文件名和所在行數。
calldepth == 0
,表示獲取調用runtime.Caller(calldepth)
的文件名和行數calldepth == 1
,表示獲取調用std.Output()
的文件名和行數calldepth == 2
,表示獲取調用ll.logf()
的文件名和行數calldepth == 3
,表示獲取調用logger.Info()
的文件名和行數calldepth == 4
,表示獲取調用klog.Info()
的文件名和行數(也就是main.go
文件)
基於klog再度進行封裝,在打印日誌獲取文件名時可能會有問題,下面是摘自Kitex文檔的一句描述:
猜測原因就是klog
的封裝,固定了calldepth == 4
,確保其在獲取文件信息時能定位到main.go
文件中,而如果對klog
再封幾層,會導致calldepth
需要更大才能定位到最外層main.go
文件,而這個值並不能通過klog
的提供的實現進行修改。
在初始化時通過log.New()
函數指定了日誌輸出的位置和需要打印的前置信息(文件名、行數、日期)
定製自己的Logger
可以使用klog.SetLogger()
來替換掉默認的logger
實現,需要傳入一個實現了所有FullLogger
接口中定義的方法的實例。
值得注意的是:SetLogger()
函數並非是並發安全的,這個方法不應該在你使用了默認的defaultLogger
定義實例之後再去使用(會覆蓋掉默認的logger
實例)。
當然完全重新定製比較複雜,大多數時候,我們只需要在默認的logger
基礎上重定向日誌輸出或者修改默認日誌級別即可:
下面修改日誌打印級別為Notice
(高於Info
),並且重定向日誌的輸出:
這裡指定了日誌輸出到文件log.txt
中,並且因為Info
級別低於聲明的Notice
,因此日誌輸出操作被忽略:
小結
通過分析,我們發現klog
在Go原生log
庫的基礎上,進行了精簡的二次封裝,一定程度上約束了打印的日誌的內容為:日期 + 時間微秒級 + 調用文件名 + 所在行數 + 日誌級別 + 格式化的日誌內容,使用十分便捷。
當然它也提供了SetLogger()
方法去供你自己實現logger
實例,以滿足更多的定製化需求。