GO語言框架快速集成日志模塊的操作方法
在我們的日常開發(fā)中, 日志模塊永遠是最基礎且最重要的一個模塊, 它可以有效的幫我們發(fā)現(xiàn)問題, 定位問題, 最后去解決問題;
zap包的集成
簡介
zap
是一個可以在go項目中進行快速, 結構化且分級的日志記錄包, git star數(shù)高達16.3k, Git 項目地址, 在各大公司項目中被廣泛使用;
最基礎的使用
package main import ( "go.uber.org/zap" "time" ) func main() { logger, _ := zap.NewProduction() defer logger.Sync() logger.Info(" log info msg", zap.String("name", "掘金"), zap.Int("num", 3), zap.Duration("timer", time.Minute), ) }
{"level":"info","ts":1657600159.826612,"caller":"log/main.go:11","msg":" log info msg","name":"腳本","num":3,"timer":60} {"level":"warn","ts":1657600159.8266969,"caller":"log/main.go:16","msg":" this is err msg","msg":"code err"}
可以看到上面就是打印出來的log, 當然這是用的默認配置, 所以格式和輸出可能不太符合我們的要求, 我們可以自己修改配置來完成定制化log;
定制化
// NewProduction builds a sensible production Logger that writes InfoLevel and // above logs to standard error as JSON. // // It's a shortcut for NewProductionConfig().Build(...Option). func NewProduction(options ...Option) (*Logger, error) { return NewProductionConfig().Build(options...) }
可以看到生成log的方法其實就是用 config
和 build
來構造一個記錄器, 我們試試自定義一下;
package main import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" ) func main() { conf := zap.NewProductionConfig() // 可以把輸出方式改為控制臺編碼, 更容易閱讀 conf.Encoding = "console" // 時間格式自定義 conf.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString("[" + t.Format("2006-01-02 15:04:05") + "]") } // 打印路徑自定義 conf.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString("[" + caller.TrimmedPath() + "]") } // 級別顯示自定義 conf.EncoderConfig.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString("[" + level.String() + "]") } logger, _ := conf.Build() logger.Info("service start") logger.Info("info msg", zap.String("name", "掘金"), zap.Int("num", 3), zap.Duration("timer", time.Minute), ) }
[2022-07-12 14:57:18][info] [log/main.go:28] service start [2022-07-12 14:57:18][info] [log/main.go:30] info msg {"name": "掘金", "num": 3, "timer": 60}
這種日志一般大家看起來就比較舒服了, 特別是打印json
結構的話可以直接復制解析器里面去看, 沒有json
編碼的各種轉義;
zap
包還是很靈活的, 基本上配置參數(shù)都支持自定義(輸出鍵名, 格式等等), 大家可以按需配置使用;
// An EncoderConfig allows users to configure the concrete encoders supplied by // zapcore. type EncoderConfig struct { // Set the keys used for each log entry. If any key is empty, that portion // of the entry is omitted. MessageKey string `json:"messageKey" yaml:"messageKey"` LevelKeystring `json:"levelKey" yaml:"levelKey"` TimeKey string `json:"timeKey" yaml:"timeKey"` NameKey string `json:"nameKey" yaml:"nameKey"` CallerKey string `json:"callerKey" yaml:"callerKey"` FunctionKeystring `json:"functionKey" yaml:"functionKey"` StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` LineEnding string `json:"lineEnding" yaml:"lineEnding"` // Configure the primitive representations of common complex types. For // example, some users may want all time.Times serialized as floating-point // seconds since epoch, while others may prefer ISO8601 strings. EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` EncodeCallerCallerEncoder`json:"callerEncoder" yaml:"callerEncoder"` // Unlike the other primitive type encoders, EncodeName is optional. The // zero value falls back to FullNameEncoder. EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` // Configures the field separator used by the console encoder. Defaults // to tab. ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` }
進階封裝
項目里面生產環(huán)境為了方便收集日志可能都比較喜歡用json
, 開發(fā)環(huán)境中大家又都比較喜歡console
方便調試, 還有比如希望接口/服務日志帶上屬于自己的唯一請求標識這種, 以及對日志進行分類保存等等, 就需要對zap
包進行再一次的封裝;
下面是我自己封裝的log
包代碼, 可以很方便的解決上面的問題, 大家可以當做參考;
index.go
記錄器初始化, 根據配置選擇編碼輸出方式及日志文件保存邏輯, 以及一些自己自定義的輸出標準;
package logging import ( "go.uber.org/zap" "go.uber.org/zap/buffer" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" "os" "path" "strings" ) type ( Conf struct { Path string // 日志路徑 Encoder string // 編碼器選擇 } logItem struct { FileName string Level zap.LevelEnablerFunc } Encoder interface { Config() zapcore.Encoder WithKey(key string) Encoder WithField(key, val string) Encoder Debug(msg string) Debugf(format string, v ...interface{}) Info(msg string) Infof(format string, v ...interface{}) Warn(msg string) Warnf(format string, v ...interface{}) Error(msg string) Errorf(format string, v ...interface{}) Fatal(msg string) Fatalf(format string, v ...interface{}) } ) var ( maxSize = 200 // 每個日志文件最大尺寸200M maxBackups = 20 // 日志文件最多保存20個備份 maxAge = 30 // 保留最大天數(shù) _logger *zap.Logger _pool= buffer.NewPool() c Conf ConsoleEncoder = "console" // 控制臺輸出 JsonEncoder = "json" // json輸出 ) // Init 初始化日志. func Init(conf Conf) { c = conf prefix, suffix := getFileSuffixPrefix(c.Path) infoPath := path.Join(prefix + ".info" + suffix) errPath := path.Join(prefix + ".err" + suffix) items := []logItem{ { FileName: infoPath, Level: func(level zapcore.Level) bool { return level <= zap.InfoLevel }, }, { FileName: errPath, Level: func(level zapcore.Level) bool { return level > zap.InfoLevel }, }, } NewLogger(items) } // NewLogger 日志. func NewLogger(items []logItem) { var ( cfgzapcore.Encoder cores []zapcore.Core ) switch c.Encoder { case JsonEncoder: cfg = NewJsonLog().Config() case ConsoleEncoder: cfg = NewConsoleLog().Config() default: cfg = NewConsoleLog().Config() } for _, v := range items { hook := lumberjack.Logger{ Filename:v.FileName, MaxSize: maxSize, // 每個日志文件保存的最大尺寸 單位:M MaxBackups: maxBackups, // 日志文件最多保存多少個備份 MaxAge: maxAge, // 文件最多保存多少天 Compress:true, // 是否壓縮 LocalTime: true, // 備份文件名本地/UTC時間 } core := zapcore.NewCore( cfg, // 編碼器配置; zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制臺和文件 v.Level, // 日志級別 ) cores = append(cores, core) } // 開啟開發(fā)模式,堆棧跟蹤 caller := zap.AddCaller() // 開發(fā)模式 development := zap.Development() // 二次封裝 skip := zap.AddCallerSkip(1) // 構造日志 _logger = zap.New(zapcore.NewTee(cores...), caller, development, skip) return } // GetEncoder 獲取自定義編碼器. func GetEncoder() Encoder { switch c.Encoder { case JsonEncoder: return NewJsonLog() case ConsoleEncoder: return NewConsoleLog() default: return NewConsoleLog() } } // GetLogger 獲取日志記錄器. func GetLogger() *zap.Logger { return _logger } // getFileSuffixPrefix 文件路徑切割 func getFileSuffixPrefix(fileName string) (prefix, suffix string) { paths, _ := path.Split(fileName) base := path.Base(fileName) suffix = path.Ext(fileName) prefix = strings.TrimSuffix(base, suffix) prefix = path.Join(paths, prefix) return } // getFilePath 自定義獲取文件路徑. func getFilePath(ec zapcore.EntryCaller) string { if !ec.Defined { return "undefined" } buf := _pool.Get() buf.AppendString(ec.Function) buf.AppendByte(':') buf.AppendInt(int64(ec.Line)) caller := buf.String() buf.Free() return caller }
console.go
控制臺編碼輸出, 支持自定義;
package logging import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" ) type ConsoleLog struct { val string } func NewConsoleLog() Encoder { return new(ConsoleLog) } // Config 自定義配置. func (slf *ConsoleLog) Config() zapcore.Encoder { var ( cfg = zap.NewProductionEncoderConfig() ) // 時間格式自定義 cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString("[" + t.Format("2006-01-02 15:04:05") + "]") } // 打印路徑自定義 cfg.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString("[" + getFilePath(caller) + "]") } // 級別顯示自定義 cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString("[" + level.String() + "]") } return zapcore.NewConsoleEncoder(cfg) } // WithKey 添加單個鍵. func (slf *ConsoleLog) WithKey(key string) Encoder { slf.val = slf.val + "[" + key + "] " return slf } // WithField 添加字段. func (slf *ConsoleLog) WithField(key, val string) Encoder { slf.val = slf.val + fmt.Sprintf("[%s:%s] ", key, val) return slf } func (slf *ConsoleLog) Debug(msg string) { _logger.Debug(slf.val + msg) } func (slf *ConsoleLog) Debugf(format string, v ...interface{}) { _logger.Debug(fmt.Sprintf(slf.val+format, v...)) } func (slf *ConsoleLog) Info(msg string) { _logger.Info(slf.val + msg) } func (slf *ConsoleLog) Infof(format string, v ...interface{}) { _logger.Info(fmt.Sprintf(slf.val+format, v...)) } func (slf *ConsoleLog) Warn(msg string) { _logger.Warn(slf.val + msg) } func (slf *ConsoleLog) Warnf(format string, v ...interface{}) { _logger.Warn(fmt.Sprintf(slf.val+format, v...)) } func (slf *ConsoleLog) Error(msg string) { _logger.Error(slf.val + msg) } func (slf *ConsoleLog) Errorf(format string, v ...interface{}) { _logger.Error(fmt.Sprintf(slf.val+format, v...)) } func (slf *ConsoleLog) Fatal(msg string) { _logger.Fatal(slf.val + msg) } func (slf *ConsoleLog) Fatalf(format string, v ...interface{}) { _logger.Fatal(fmt.Sprintf(slf.val+format, v...)) }
json.go
json
編碼輸出, 支持自定義;
package logging import ( "fmt" "go.uber.org/zap/zapcore" "time" "go.uber.org/zap" ) type JsonLog struct { fields []zap.Field val string } // NewJsonLog 自定義添加log field. func NewJsonLog() Encoder { return &JsonLog{fields: make([]zap.Field, 0)} } // Config 自定義配置. func (slf *JsonLog) Config() zapcore.Encoder { var ( cfg = zap.NewProductionEncoderConfig() ) // 時間格式自定義 cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05")) } // 打印路徑自定義 cfg.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString(getFilePath(caller)) } // 級別顯示自定義 cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString(level.String()) } return zapcore.NewJSONEncoder(cfg) } // WithKey 添加單個鍵. func (slf *JsonLog) WithKey(key string) Encoder { slf.val = slf.val + key + " " return slf } // WithField 添加字段. func (slf *JsonLog) WithField(key, val string) Encoder { slf.fields = append(slf.fields, zap.String(key, val)) return slf } func (slf *JsonLog) Debug(msg string) { _logger.Debug(slf.val+msg, slf.fields...) } func (slf *JsonLog) Debugf(format string, v ...interface{}) { _logger.Debug(fmt.Sprintf(slf.val+format, v...), slf.fields...) } func (slf *JsonLog) Info(msg string) { _logger.Info(slf.val+msg, slf.fields...) } func (slf *JsonLog) Infof(format string, v ...interface{}) { _logger.Info(fmt.Sprintf(slf.val+format, v...), slf.fields...) } func (slf *JsonLog) Warn(msg string) { _logger.Warn(slf.val+msg, slf.fields...) } func (slf *JsonLog) Warnf(format string, v ...interface{}) { _logger.Warn(fmt.Sprintf(slf.val+format, v...), slf.fields...) } func (slf *JsonLog) Error(msg string) { _logger.Error(slf.val+msg, slf.fields...) } func (slf *JsonLog) Errorf(format string, v ...interface{}) { _logger.Error(fmt.Sprintf(slf.val+format, v...), slf.fields...) } func (slf *JsonLog) Fatal(msg string) { _logger.Fatal(slf.val+msg, slf.fields...) } func (slf *JsonLog) Fatalf(format string, v ...interface{}) { _logger.Fatal(fmt.Sprintf(slf.val+format, v...), slf.fields...) }
service.go
標準輸出方法, 方便直接調用;
package logging import ( "fmt" ) func Sync() { _ = _logger.Sync() } func Debug(msg string) { _logger.Debug(msg) } func Debugf(format string, v ...interface{}) { _logger.Debug(fmt.Sprintf(format, v...)) } func Info(msg string) { _logger.Info(msg) } func Infof(format string, v ...interface{}) { _logger.Info(fmt.Sprintf(format, v...)) } func Warn(msg string) { _logger.Warn(msg) } func Warnf(format string, v ...interface{}) { _logger.Warn(fmt.Sprintf(format, v...)) } func Error(msg string) { _logger.Error(msg) } func Errorf(format string, v ...interface{}) { _logger.Error(fmt.Sprintf(format, v...)) } func Fatal(msg string) { _logger.Fatal(msg) } func Fatalf(format string, v ...interface{}) { _logger.Fatal(fmt.Sprintf(format, v...)) }
上面的進階代碼摘自我開發(fā)的一個git項目中, 主要是一個Go
的標準項目布局, 封裝了一些常用的組件, 有興趣的朋友可以了解一下, 對新手還是很友好的;
到此這篇關于GO語言框架快速集成日志模塊的操作方法的文章就介紹到這了,更多相關go語言日志模塊內容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持本站!
版權聲明:本站文章來源標注為YINGSOO的內容版權均為本站所有,歡迎引用、轉載,請保持原文完整并注明來源及原文鏈接。禁止復制或仿造本網站,禁止在非www.sddonglingsh.com所屬的服務器上建立鏡像,否則將依法追究法律責任。本站部分內容來源于網友推薦、互聯(lián)網收集整理而來,僅供學習參考,不代表本站立場,如有內容涉嫌侵權,請聯(lián)系alex-e#qq.com處理。