一箍铭、背景
后端開發(fā)過程中,經(jīng)常會(huì)涉及到日志框架的選取問題椎镣,對(duì)于go項(xiàng)目诈火,類似框架也很多,eg:zap状答、zerolog冷守、logrus等。由于讀寫日志都是一個(gè)比較頻繁的操作惊科,因此性能是我們首先考慮的問題拍摇。在此我選取的是zap日志包。zap由uber開源馆截,由于其快速充活、結(jié)構(gòu)化蜂莉、高性能等優(yōu)點(diǎn),而受到眾程序員的熱愛混卵,我們來看幾張由Zap官方提供的基準(zhǔn)測試對(duì)照表:
日志性能對(duì)照表1
日志性能對(duì)照表2
日志性能對(duì)照表3
從中我們可以看出zerolog是與Zap競爭最激烈的映穗。zerolo還提供結(jié)果非常相似的基準(zhǔn)測試。
二幕随、自定義zap日志
我們來看下zap日志包的工作流程圖:
zap日志包工作流程圖
基于此蚁滋,我整理以下自定義zap日志常規(guī)思路:
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var logger *zap.Logger
func main() {
// 6 初始化logger
GetLogger()
// 7 打印日志
logger.Info("自定義logger", zap.String("name", "zap log"))
logger.Debug("自定義logger", zap.String("name", "zap log"))
}
func GetLogger() {
// 1 用new自定義log日志
// zap.New(xxx)
// 2 zap.New需要接收一個(gè)core,core是zapcore.Core類型赘淮,zapcore.Core是一個(gè)interface類型辕录,
// 而zapcore.NewCore返回的ioCore剛好實(shí)現(xiàn)了這個(gè)接口類型的所有5個(gè)方法,那么NewCore也可以認(rèn)為是core類型
// 3 所以zap.New(core)變成了zap.New(zapcore.NewCore)
// 4 而zapcore.NewCore需要三個(gè)變量:Encoder, WriteSyncer, LevelEnabler,我們在創(chuàng)建NewCore時(shí)自定義這三個(gè)類型變量即可梢卸,其中:
// Encoder:編碼器 (寫入日志格式)
// WriteSyncer:指定日志寫到哪里去
// LevelEnabler:日志打印級(jí)別
// NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler)
// 4.2 通過GetEncoder獲取自定義的Encoder
Encoder := GetEncoder()
// 4.4 通過GetWriteSyncer獲取自定義的WriteSyncer
WriteSyncer := GetWriteSyncer()
// 4.6 通過GetLevelEnabler獲取自定義的LevelEnabler
LevelEnabler := GetLevelEnabler()
// 4.7 通過Encoder走诞、WriteSyncer、LevelEnabler創(chuàng)建一個(gè)core
newCore := zapcore.NewCore(Encoder, WriteSyncer, LevelEnabler)
// 5 傳遞 newCore New一個(gè)logger
// zap.AddCaller(): 輸出文件名和行號(hào)
// zap.Fields: 假如每條日志中需要攜帶公用的信息低剔,可以在這里進(jìn)行添加
logger = zap.New(newCore)
}
// GetEncoder 自定義的Encoder 4.1
// 打開zapcore的源碼速梗,見圖“zapcore-Encoder”:發(fā)現(xiàn)其中有兩個(gè)new Encoder的func:
// NewConsoleEncoder(console_encoder.go)
// NewJSONEncoder(json_encoder.go)
// 這兩個(gè)func都需要傳遞一個(gè)EncoderConfig的變量,而zap中已經(jīng)給我們提供了幾種獲取EncoderConfig的方式
// zap.NewProductionEncoderConfig()
// zap.NewDevelopmentEncoderConfig()
// 在這里我直接把zap.NewProductionEncoderConfig()源碼中的部分黏貼過來
func GetEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(
zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding, // 默認(rèn)換行符"\n"
EncodeLevel: zapcore.LowercaseLevelEncoder, // 日志等級(jí)序列為小寫字符串襟齿,如:InfoLevel被序列化為 "info"
EncodeTime: zapcore.EpochTimeEncoder, // 日志時(shí)間格式顯示
EncodeDuration: zapcore.SecondsDurationEncoder, // 時(shí)間序列化姻锁,Duration為經(jīng)過的浮點(diǎn)秒數(shù)
EncodeCaller: zapcore.ShortCallerEncoder, // 日志行號(hào)顯示
})
}
// GetWriteSyncer 自定義的WriteSyncer 4.3
func GetWriteSyncer() zapcore.WriteSyncer {
file, _ := os.Create("./zap.log")
return zapcore.AddSync(file)
}
// GetLevelEnabler 自定義的LevelEnabler 4.5
func GetLevelEnabler() zapcore.Level {
return zapcore.InfoLevel // 只會(huì)打印出info及其以上級(jí)別的日志
}
zapcore-Encoder
# 上述腳本輸出的日志格式如下:
1.6475221235018082e+09 info 自定義logger {"name": "zap log"}
三、擴(kuò)展猜欺、完善
對(duì)于上面“二”其實(shí)還有很多擴(kuò)展和完善的地方位隶,如下:
3.1、我們通過上面方式定義的logger开皿,每次打印日志的時(shí)候涧黄,需要這樣操作:logger.Info、logger.Debug等赋荆,如果zap內(nèi)有全局的logger笋妥,然后我們將自己定義的logger替換成全局的logger,此后打印日志的時(shí)候窄潭,全局logger是否有一種更為簡便的方式春宣。
3.2、如果將文件同時(shí)輸出到控制臺(tái)和文件
3.3嫉你、文件過大如何切分
3.4月帝、如下格式的日志,如何自定義:
[2022-03-17 21:32:24] [INFO] [log_demo/main.go:21] 自定義logger
3.5幽污、在gin框架中如何載入自定義的日志
對(duì)于上面的問題嚷辅,逐一解答:
3.1、為了方便使用距误,zap提供了兩個(gè)全局的Logger簸搞,一個(gè)是*zap.Logger扁位,可調(diào)用zap.L()獲得;另一個(gè)是*zap.SugaredLogger趁俊,可調(diào)用zap.S()獲得贤牛。需要注意的是,全局的Logger默認(rèn)并不會(huì)記錄日志则酝!它是一個(gè)無實(shí)際效果的Logger∪蚣看下源碼:
// [go.uber.org/zap/global.go](http://go.uber.org/zap/global.go)
var (
_globalMu sync.RWMutex
_globalL = NewNop()
_globalS = _globalL.Sugar()
)
3.2沽讹、使用zapcore.NewTee
3.3、使用github.com/natefinch/lumberjack包
3.4武鲁、分析zapcore.LowercaseLevelEncoder爽雄、zapcore.EpochTimeEncoder、zapcore.ShortCallerEncoder進(jìn)行充血
3.5沐鼠、gin中使用上面自定義的logger見下代碼
最終代碼見下:
package main
import (
"github.com/gin-gonic/gin"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net/http"
"os"
"time"
)
const (
logTmFmt = "2006-01-02 15:04:05"
)
func main() {
GetLogger()
r := gin.New()
r.Use(GinLogger())
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"version": "v1.1",
})
})
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "404",
})
})
r.Run(":9090")
}
func GinLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
zap.L().Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
func GetLogger() {
Encoder := GetEncoder()
WriteSyncer := GetWriteSyncer()
LevelEnabler := GetLevelEnabler()
ConsoleEncoder := GetConsoleEncoder()
newCore := zapcore.NewTee(
zapcore.NewCore(Encoder, WriteSyncer, LevelEnabler), // 寫入文件
zapcore.NewCore(ConsoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), // 寫入控制臺(tái)
)
logger := zap.New(newCore, zap.AddCaller())
zap.ReplaceGlobals(logger)
}
// GetEncoder 自定義的Encoder
func GetEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(
zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller_line",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: " ",
EncodeLevel: cEncodeLevel,
EncodeTime: cEncodeTime,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: cEncodeCaller,
})
}
// GetConsoleEncoder 輸出日志到控制臺(tái)
func GetConsoleEncoder() zapcore.Encoder {
return zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
}
// GetWriteSyncer 自定義的WriteSyncer
func GetWriteSyncer() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./zap.log",
MaxSize: 200,
MaxBackups: 10,
MaxAge: 30,
}
return zapcore.AddSync(lumberJackLogger)
}
// GetLevelEnabler 自定義的LevelEnabler
func GetLevelEnabler() zapcore.Level {
return zapcore.InfoLevel
}
// cEncodeLevel 自定義日志級(jí)別顯示
func cEncodeLevel(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + level.CapitalString() + "]")
}
// cEncodeTime 自定義時(shí)間格式顯示
func cEncodeTime(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + t.Format(logTmFmt) + "]")
}
// cEncodeCaller 自定義行號(hào)顯示
func cEncodeCaller(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + caller.TrimmedPath() + "]")
}
日志格式見下(控制臺(tái)和文件中都會(huì)輸出):
[2022-03-17 22:31:59] [INFO] [log_demo/main.go:41] / {"status": 200, "method": "GET", "path": "/", "query": "", "ip": "127.0.0.1", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36", "errors": "", "cost": 0.000111808}