如何使用GO語言從零實現(xiàn)日志包
背景
當(dāng)前的開源日志包有很多,像go中的標(biāo)準(zhǔn)庫log包、glog、logrus、zap。它們每種日志包都有相應(yīng)的應(yīng)用場景。四種日志包相關(guān)對比如下所示:
標(biāo)準(zhǔn)庫log | 功能簡單,不支持日志級別、日志格式。但是使用簡單,易于快速上手。大型項目較少使用 |
glog | 提供了日志包的基本功能,像日志級別、格式等。適合一些小項目 |
logrus | 功能強大,不僅實現(xiàn)了基本日志功能,還提供了很多高級功能。適合大型項目 |
zap | 功能強大,性能高,適合對日志性能要求高的項目。另外zap的子包zapcore提供了很多底層日志接口,適合二次開發(fā) |
從頭開發(fā)一個日志包,可以讓我們了解日志包的底層邏輯,使得我們對日志包有定制需求的時候,可以能夠基于開源的日志包實現(xiàn)我們的功能。所以本文以cuslog為例,看看如何實現(xiàn)我們自己的日志包(代碼:
https://github.com/marmotedu/gopractise-demo/tree/master/log/cuslog)。
代碼結(jié)構(gòu)
cuslog目錄代碼結(jié)構(gòu)
cuslog代碼結(jié)構(gòu)
要實現(xiàn)一個日志包,就需要實現(xiàn)下面三個基本的對象,Entry,Logger,Options。
Entry
代碼:
https://github.com/marmotedu/gopractise-demo/blob/master/log/cuslog/entry.go。
Entry的write方法實現(xiàn)了將它Buffer中的數(shù)據(jù),寫入到它的logger所配置的output中。
Logger
代碼:
https://github.com/marmotedu/gopractise-demo/blob/master/log/cuslog/logger.go。
Options
代碼:
https://github.com/marmotedu/gopractise-demo/blob/68e100ee78a3093e6f2434439e7d4b143b9ebf60/log/cuslog/options.go。
應(yīng)用
通過下面的代碼,我們看看整個代碼是如何串起來的。
- 上面的整體實現(xiàn)是,將"custom log with json formatter"這段字符串寫入到指定文件里面
- 1到6行創(chuàng)建并打開文件
- 重點是8行,cuslog.WithLevel(cuslog.InfoLevel)、cuslog.WithOutput(fd)、cuslog.WithFormatter(&cuslog.JsonFormatter{IgnoreBasicFields: false})三個函數(shù)調(diào)用,返回三個函數(shù)func(o *options),這些函數(shù)都是Option類型,因為type Option func(*options)。
- 然后調(diào)用cuslog.New,這個函數(shù)上面也給出了,它里面通過initOptions依次調(diào)用上面的Option函數(shù),對options對象進行設(shè)置,然后把options賦給opt, 并創(chuàng)建logger. logger := &logger{opt: initOptions(opts...)}
- 最后調(diào)用l.Info("custom log with json formatter")把字符串輸出到文件中.整個調(diào)用鏈?zhǔn)莑.info===>通過l的pool獲取entry===>調(diào)用entry的write(InfoLevel, FmtEmptySeparate, args...),在這個write函數(shù)里面,只有infoLevel比logger.level優(yōu)先級大或相等,才輸出。并且通過runtime.Caller(2)獲取最上層調(diào)用info時的,文件名,行號,函數(shù)名等信息。因為這個地方有2層嵌套才調(diào)用到entry的write,所以runtime.Caller(2)的參數(shù)是2
總結(jié)
上面的代碼實現(xiàn)了基本的日志功能,包括日志級別、日志格式配置、輸出文件或標(biāo)準(zhǔn)輸出的設(shè)置。但是一些高級的功能,比如按級別分類輸出,Hook能力,結(jié)構(gòu)化日志。這些目前都不支持。