半熟前端

軟體工程師 / 台灣人 / 在前端的路上一邊探索其他領域的可能性

golang

如何搜集並集中 golang 應用中的 Log

這篇文章是看完 How to collect, standardize, and centralize Golang logs | Datadog 總結的心得。

使用 log 時通常有幾點要注意:

  1. 把 log 當作參數傳遞,需要使用到 log 時就傳參數進去
  2. 統一用 context 封裝,要用的時候從 context 拿出來
  3. 把 logger 包裝成 package,並且暴露一個變數給其他 package 使用

很顯然地,第一個做法很快會遇到問題,就是你的參數會長得相當奇怪,舉例來說:

func (p *Post) CreatePost(name string, content string, logger *log.Logger) {
  ///
  if err != nil {
    logger.Fatal("error!")
  }
}

一個 CreatePost 的邏輯竟然要傳一個 logger,很容易就把你的參數用得髒髒的,也可能出現層層傳遞 logger 的情形,這樣最大的好處就是依賴是透明的,透過函數可以明確知道裡頭會使用 logger,要測試也很容易就能把 logger 給 mock 掉。

我們可以採用第二個做法,在 main 當中顯式地傳入 context,並把 logger 塞在裡頭。需要取用時就 log.Logger.FromContext(ctx)。目前還沒有想到特別明顯的缺點。

const (
  ContextKeyLogger = "logger"
)
func main() {

  ctx := context.Background()
  loggerCtx := context.WithValue(ctx, ContextKeyLogger, log.Logger)
  
}

第三種做法是我目前的做法,就是將 logger 包裝成 package,並且暴露一個變數給其他 package 使用。這樣子最大的好處就是方便,隨插即用,缺點是如果要測試的時候就不是那麼容易。因為沒有被參數化,logger 很難被 mock 掉。(或許也有其他好的解法?)

package logging

var Logger *log.Logger

func init() {
  Logger = &log.Logger{
    // your setting
  }
}

接下來我們來看看這篇文章中的建議:

  1. 建議使用 logrus。logrus 是一個非常好用的 logging 套件,而且完美支援 原生的 log,完全可以無痛轉換。搭配 hook 機制你可以很容易串接第三方的回報平台(像 datadog, sentry 等等)
  2. 使用 JSON 當作紀錄檔的格式:JSON 相當好解析,各大語言都支持,對第三方平台來說也好分析,非常推薦使用,在 logrus 當中你可以直接 SetFormatter(&logrus.JSONFormatter{}) 來設定 output
  3. 使用統一介面。
  4. 盡量不要在 goroutine 當中呼叫 logger,除了要考慮 concurrent 的問題之外,因為 logger 內部實作可能也有跑 goroutine,所以會讓整個機制不好掌握。
  5. 將 log 存成 local 檔案,儘管你可能有使用其他平台。這樣不會因為 network 問題導致某些紀錄喪失,也可以確保總是找得到紀錄檔
  6. 如果是用像 cluster 的架構,可能會需要 syslog 的方式統一集中在一台紀錄檔的伺服器中
  7. 如果用像是 docker 之類的容器服務,可能需要監聽 docker 的 STDOUT。

其他

log.Fatal()log.Panic(),其中 Fatal 會偷偷幫你呼叫 os.Exit(1),好啦我知道文件上有些但可能很多人都沒看,Panic 則是偷偷幫你呼叫 panic,在使用上要特別小心。