這篇文章是看完 How to collect, standardize, and centralize Golang logs | Datadog 總結的心得。
使用 log 時通常有幾點要注意:
- 把 log 當作參數傳遞,需要使用到 log 時就傳參數進去
- 統一用 context 封裝,要用的時候從 context 拿出來
- 把 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
}
}
接下來我們來看看這篇文章中的建議:
- 建議使用 logrus。logrus 是一個非常好用的 logging 套件,而且完美支援 原生的
log
,完全可以無痛轉換。搭配 hook 機制你可以很容易串接第三方的回報平台(像 datadog, sentry 等等) - 使用 JSON 當作紀錄檔的格式:JSON 相當好解析,各大語言都支持,對第三方平台來說也好分析,非常推薦使用,在 logrus 當中你可以直接
SetFormatter(&logrus.JSONFormatter{})
來設定 output - 使用統一介面。
- 盡量不要在 goroutine 當中呼叫 logger,除了要考慮 concurrent 的問題之外,因為 logger 內部實作可能也有跑 goroutine,所以會讓整個機制不好掌握。
- 將 log 存成 local 檔案,儘管你可能有使用其他平台。這樣不會因為 network 問題導致某些紀錄喪失,也可以確保總是找得到紀錄檔
- 如果是用像 cluster 的架構,可能會需要
syslog
的方式統一集中在一台紀錄檔的伺服器中 - 如果用像是 docker 之類的容器服務,可能需要監聽 docker 的 STDOUT。
其他
log.Fatal()
跟 log.Panic()
,其中 Fatal
會偷偷幫你呼叫 os.Exit(1)
,好啦我知道文件上有些但可能很多人都沒看,Panic
則是偷偷幫你呼叫 panic
,在使用上要特別小心。