最近在嘗試閱讀位元組開源RPC框架Kitex的原始碼,看到紀錄檔庫klog
部分,果不其然在Go原生的log庫
的基礎上增加了自己的設計,大體包括增加了一些格式化的輸出、增加一些常用的紀錄檔級別等。
一番瞭解後,發現有不少開源的紀錄檔庫也做了類似的事情,以補充原生log庫
的不足。因為Go原生的log庫
本身也比較簡單,這篇文章先分析一下它的實現,為後續閱讀Kitex的紀錄檔庫klog
做一下鋪墊。
本次分析基於:GO SDK 1.18.1 /src/log/log.go
的原始碼。
結果如下:
第三個紀錄檔因為第二個紀錄檔列印之後,呼叫panic()
函數,且沒有呼叫recover()
,導致程式終止。如果註釋掉第二行紀錄檔即可列印出第三個紀錄檔的結果如下:
通過觀察原始碼,log包
的log.go
檔案中,提供了9個函數可以直接使用,3個一套,分別針對print型
紀錄檔輸出、panic型
紀錄檔輸出(可以recover
)、fatal型
紀錄檔輸出(直接終止程式)。
並且這9個函數中頻繁使用到了一個std範例
,只要我們引入了log包
,std
就會完成初始化,並且作為預設使用的log
範例。
既然std
是預設的Logger範例
,這裡先看一下Logger
的結構:
mu
:互斥鎖,用於原子寫入操作。prefix
:紀錄檔字首/字尾。flag
:控制需要展示的紀錄檔內容。out
:描述輸出。buf
:緩衝區。關於flag
的使用,Go定義瞭如下的常數:
iota
是常數計數器,從0開始自增,可以配合表示式使用,且在一系列常數宣告時,可以只指定第一個位置,後續會預設初始化,這裡依次初始化為1、2、4...
Ldata
:輸出當地日期,如2009/01/23。
Ltime
:輸出當地時間,如01:23:23。
Lmicroseconds
:時間精確到微妙,如01:23:23.123123
,兼併Ltime。
Llongfile
:輸出檔名全路徑 + 呼叫行號,如/a/b/c/d.go:23。
Lshortfile
:輸出最終檔案的名稱 + 呼叫行號,如d.go:23
,覆蓋Llongfile
。LUTC
:如果設定了Ldata
和Ltime
,則將輸出UTC
時間,而不是本地時區。Lmsgprefix
:將prefix資訊
從當前紀錄檔行首部移動到message
之前。LstdFlags
:std範例
的預設值,表示Ldata | Ltime = 3
。官方的註釋中給出了一些介紹flag用法的例子,這裡介紹一個:
如果:std.flag == Ldate | Ltime | Lmicroseconds | Llongfile == 15
,
則紀錄檔行輸出結果為:2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
,message
為具體的紀錄檔內容。
回到上面9個函數列印紀錄檔,都通過呼叫std.Output()
實現紀錄檔的輸出,是log庫
的核心函數,看一下程式碼:
l.mu.Lock()
,確保紀錄檔內容的寫入是原子的。l.flag
是否包括Lshortfile
或者Llongfile
標誌位,如果有則需要獲取檔名
和行數
,且這一步先釋放了鎖,因為Caller方法
的呼叫比較耗時(expensive),確保鎖住的臨界區儘可能小。calldepth
:0表示獲取呼叫runtime.Caller(calldepth)
的檔名和行數,1表示呼叫std.Output()
的檔名和函數,2表示呼叫log.Println()
的檔名和行數,3則已經用不到了,Go原生log庫獲取行資訊用的都是2。l.buf
,並格式化紀錄檔頭部資訊(日期、檔名、行數),將其append
入`buf。紀錄檔資訊s
新增入buf
,會補全末尾換行符,並呼叫l.out.Write()
,將紀錄檔寫入事先註冊的輸出檔案。log庫預設使用的std範例是事先初始化好的,那麼藉助New方法,我們也可以客製化自己的logger:
這裡指定了紀錄檔輸出到檔案log.txt中,並且定義了一些flag,結果如下:
通過分析,我們發現log是一個很簡潔的紀錄檔庫,它有三種紀錄檔輸出方式print
、panic
、fatal
,且可以自己客製化紀錄檔的輸出格式。但是熟悉其他語言開發的同學可能會對紀錄檔級別有更多的需求,且log
的格式化用起來比較複雜。
因此會衍生出很多基於log
的二次封裝的紀錄檔庫,下一篇文章將講解位元組跳動RPC框架Kitex的紀錄檔庫klog
的實現。