位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

白澤來了發表於2022-06-06

前言

這篇文章將著重於分析位元組跳動開源的RPC框架Kitex的日誌庫klog的原始碼,通過對比Go原生日誌庫log的實現,探究其作出的改進。

為了平滑學習曲線,我寫下了這篇分析Go原生log庫的文章,希望你可以對比閱讀:https://juejin.cn/post/7103790667595268126

本文的分析基於:github.com/cloudwego/kitex/pkg/klog的原始碼。

klog庫的使用

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

結果如下:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

klog.xxx能直接列印日誌的原因

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

通過觀察原始碼,klog包的default.go檔案中,封裝了三類日誌的列印的函式提供直接使用:普通日誌、格式化的日誌、格式化的Context日誌

每一類包含了7個的日誌輸出級別的函式可使用:InfoDebugNoticeWarnErrorFatalTrace

並且這21個函式中頻繁使用到了一個logger例項,只要我們引入klog包,logger就會完成初始化,並且作為預設的log例項。

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

可以看到logger例項預設的日誌列印級別是LevelInfoklog通過常量計數器定義了0~6種日誌級別:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

FullLogger介面

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

預設的logger例項是通過defaultLogger結構初始化的,且defaultLogger結構實現了FullLogger介面定義的所有函式(介面定義了上面說的三類,每一類7種日誌列印函式)

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

並且觀察defaultLogger結構的屬性stdlog,就是Go原生的日誌庫log定義的Logger型別,因此klog的所有日誌操作,最終都是藉助Go原生log庫實現的。

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

相當於klog在Go原生log庫的基礎上對格式化輸出日誌列印級別作了封裝,便於直接使用。

串聯一下日誌列印函式執行的過程:

  • main函式中呼叫:klog.Info("一條普通的日誌")
  • 進一步呼叫初始化好的defaultLogger例項(名為logger)的實現自FullLogger介面的函式:logger.Info()
  • 進一步呼叫ll.logf()函式(下面重點分析

ll.logf()

上面的這三類共21個日誌列印函式最終都呼叫了ll.logf()方法,因此ll.logf()也是klog庫的核心函式,看一下程式碼:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀
  • 日誌過濾:如果呼叫的列印函式代表的日誌級別低於logger例項初始化的日誌級別,則不會列印(如預設級別是LevelInfo == 2,則呼叫klog.Trace()將被過濾)
  • 格式化列印資訊存入msg
  • 呼叫Go原生日誌庫logOutput()函式,列印日誌(這一部分在上一篇分析Go的log庫的文章中已經充分講解)

關於calldepth的問題

calldepth表示呼叫層數,這裡宣告瞭4,是為了配合獲取呼叫日誌列印函式的檔名和所在行數。

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀
  • 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文件的一句描述:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

猜測原因就是klog的封裝,固定了calldepth == 4,確保其在獲取檔案資訊時能定位到main.go檔案中,而如果對klog再封幾層,會導致calldepth需要更大才能定位到最外層main.go檔案,而這個值並不能通過klog的提供的實現進行修改。

在初始化時通過log.New()函式指定了日誌輸出的位置需要列印的前置資訊(檔名、行數、日期)

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

定製自己的Logger

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

可以使用klog.SetLogger()來替換掉預設的logger實現,需要傳入一個實現了所有FullLogger介面中定義的方法的例項。

值得注意的是:SetLogger()函式並非是併發安全的,這個方法不應該在你使用了預設的defaultLogger定義例項之後再去使用(會覆蓋掉預設的logger例項)。

當然完全重新定製比較複雜,大多數時候,我們只需要在預設的logger基礎上重定向日誌輸出或者修改預設日誌級別即可:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

下面修改日誌列印級別為Notice(高於Info),並且重定向日誌的輸出:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

這裡指定了日誌輸出到檔案log.txt中,並且因為Info級別低於宣告的Notice,因此日誌輸出操作被忽略:

位元組開源RPC框架Kitex的日誌庫klog原始碼解讀

小結

通過分析,我們發現klogGo原生log庫的基礎上,進行了精簡的二次封裝,一定程度上約束了列印的日誌的內容為:日期 + 時間微秒級 + 呼叫檔名 + 所在行數 + 日誌級別 + 格式化的日誌內容,使用十分便捷。

當然它也提供了SetLogger()方法去供你自己實現logger例項,以滿足更多的定製化需求。

相關文章