GO的日誌庫log竟然這麼簡單!

白澤來了發表於2022-05-31

前言

最近在嘗試閱讀位元組開源RPC框架Kitex的原始碼,看到日誌庫klog部分,果不其然在Go原生的log庫的基礎上增加了自己的設計,大體包括增加了一些格式化的輸出增加一些常用的日誌級別等。

一番瞭解後,發現有不少開源的日誌庫也做了類似的事情,以補充原生log庫的不足。因為Go原生的log庫本身也比較簡單,這篇文章先分析一下它的實現,為後續閱讀Kitex的日誌庫klog做一下鋪墊。

本次分析基於:GO SDK 1.18.1 /src/log/log.go的原始碼。

log庫的使用

結果如下:

image-20220530210344181

第三個日誌因為第二個日誌列印之後,呼叫panic()函式,且沒有呼叫recover(),導致程式終止。如果註釋掉第二行日誌即可列印出第三個日誌的結果如下:

image-20220530210752882

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

carbon

通過觀察原始碼,log包log.go檔案中,提供了9個函式可以直接使用,3個一套,分別針對print型日誌輸出、panic型日誌輸出(可以recover)、fatal型日誌輸出(直接終止程式)。

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

image-20220530211928675

Logger結構

既然std是預設的Logger例項,這裡先看一下Logger的結構:

image-20220531123015509

  • mu:互斥鎖,用於原子寫入操作。
  • prefix:日誌字首/字尾。
  • flag:控制需要展示的日誌內容。
  • out:描述輸出。
  • buf:緩衝區。

關於flag的使用,Go定義瞭如下的常量:

image-20220531124125730

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:如果設定了LdataLtime,則將輸出UTC時間,而不是本地時區。
  • Lmsgprefix:將prefix資訊從當前日誌行首部移動到message之前。
  • LstdFlagsstd例項的預設值,表示Ldata | Ltime = 3

官方的註釋中給出了一些介紹flag用法的例子,這裡介紹一個:

如果:std.flag == Ldate | Ltime | Lmicroseconds | Llongfile == 15

則日誌行輸出結果為:2009/01/23 01:23:23.123123 /a/b/c/d.go:23: messagemessage為具體的日誌內容。

std.Output()

回到上面9個函式列印日誌,都通過呼叫std.Output()實現日誌的輸出,是log庫的核心函式,看一下程式碼:

image-20220531122130791

  • 通過l.mu.Lock(),確保日誌內容的寫入是原子的。
  • 檢查l.flag是否包括Lshortfile或者Llongfile標誌位,如果有則需要獲取檔名行數,且這一步先釋放了鎖,因為Caller方法的呼叫比較耗時(expensive),確保鎖住的臨界區儘可能小。
  • calldepth:0表示獲取呼叫runtime.Caller(calldepth)的檔名和行數,1表示呼叫std.Output()的檔名和函式,2表示呼叫log.Println()的檔名和行數,3則已經用不到了,Go原生log庫獲取行資訊用的都是2。

image-20220531141453630

  • 清空緩衝區l.buf,並格式化日誌頭部資訊(日期、檔名、行數),將其append入`buf。
  • 最後將具體的日誌資訊s新增入buf,會補全末尾換行符,並呼叫l.out.Write(),將日誌寫入事先註冊的輸出檔案。

定製自己的Logger

image-20220531135448565

log庫預設使用的std例項是事先初始化好的,那麼藉助New方法,我們也可以定製自己的logger:

image-20220531142257578

這裡指定了日誌輸出到檔案log.txt中,並且定義了一些flag,結果如下:

image-20220531142835128

小結

通過分析,我們發現log是一個很簡潔的日誌庫,它有三種日誌輸出方式printpanicfatal,且可以自己定製日誌的輸出格式。但是熟悉其他語言開發的同學可能會對日誌級別有更多的需求,且log的格式化用起來比較複雜。

因此會衍生出很多基於log的二次封裝的日誌庫,下一篇文章將講解位元組跳動RPC框架Kitex的日誌庫klog的實現。

相關文章