GO語(yǔ)言框架快速集成日志模塊的操作方法
前言
在我們的日常開(kāi)發(fā)中, 日志模塊永遠(yuǎn)是最基礎(chǔ)且最重要的一個(gè)模塊, 它可以有效的幫我們發(fā)現(xiàn)問(wèn)題, 定位問(wèn)題, 最后去解決問(wèn)題;
zap包的集成
簡(jiǎn)介
zap
是一個(gè)可以在go項(xiàng)目中進(jìn)行快速, 結(jié)構(gòu)化且分級(jí)的日志記錄包, git star數(shù)高達(dá)16.3k, Git 項(xiàng)目地址, 在各大公司項(xiàng)目中被廣泛使用;
最基礎(chǔ)的使用
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"}
可以看到上面就是打印出來(lái)的log, 當(dāng)然這是用的默認(rèn)配置, 所以格式和輸出可能不太符合我們的要求, 我們可以自己修改配置來(lái)完成定制化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的方法其實(shí)就是用 config
和 build
來(lái)構(gòu)造一個(gè)記錄器, 我們?cè)囋囎远x一下;
package main import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" ) func main() { conf := zap.NewProductionConfig() // 可以把輸出方式改為控制臺(tái)編碼, 更容易閱讀 conf.Encoding = "console" // 時(shí)間格式自定義 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() + "]") } // 級(jí)別顯示自定義 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}
這種日志一般大家看起來(lái)就比較舒服了, 特別是打印json
結(jié)構(gòu)的話可以直接復(fù)制解析器里面去看, 沒(méi)有json
編碼的各種轉(zhuǎn)義;
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"` LevelKey string `json:"levelKey" yaml:"levelKey"` TimeKey string `json:"timeKey" yaml:"timeKey"` NameKey string `json:"nameKey" yaml:"nameKey"` CallerKey string `json:"callerKey" yaml:"callerKey"` FunctionKey string `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"` EncodeCaller CallerEncoder `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"` }
進(jìn)階封裝
項(xiàng)目里面生產(chǎn)環(huán)境為了方便收集日志可能都比較喜歡用json
, 開(kāi)發(fā)環(huán)境中大家又都比較喜歡console
方便調(diào)試, 還有比如希望接口/服務(wù)日志帶上屬于自己的唯一請(qǐng)求標(biāo)識(shí)這種, 以及對(duì)日志進(jìn)行分類保存等等, 就需要對(duì)zap
包進(jìn)行再一次的封裝;
下面是我自己封裝的log
包代碼, 可以很方便的解決上面的問(wèn)題, 大家可以當(dāng)做參考;
index.go
記錄器初始化, 根據(jù)配置選擇編碼輸出方式及日志文件保存邏輯, 以及一些自己自定義的輸出標(biāo)準(zhǔn);
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 // 每個(gè)日志文件最大尺寸200M maxBackups = 20 // 日志文件最多保存20個(gè)備份 maxAge = 30 // 保留最大天數(shù) _logger *zap.Logger _pool = buffer.NewPool() c Conf ConsoleEncoder = "console" // 控制臺(tái)輸出 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 ( cfg zapcore.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, // 每個(gè)日志文件保存的最大尺寸 單位:M MaxBackups: maxBackups, // 日志文件最多保存多少個(gè)備份 MaxAge: maxAge, // 文件最多保存多少天 Compress: true, // 是否壓縮 LocalTime: true, // 備份文件名本地/UTC時(shí)間 } core := zapcore.NewCore( cfg, // 編碼器配置; zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制臺(tái)和文件 v.Level, // 日志級(jí)別 ) cores = append(cores, core) } // 開(kāi)啟開(kāi)發(fā)模式,堆棧跟蹤 caller := zap.AddCaller() // 開(kāi)發(fā)模式 development := zap.Development() // 二次封裝 skip := zap.AddCallerSkip(1) // 構(gòu)造日志 _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
控制臺(tái)編碼輸出, 支持自定義;
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() ) // 時(shí)間格式自定義 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) + "]") } // 級(jí)別顯示自定義 cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString("[" + level.String() + "]") } return zapcore.NewConsoleEncoder(cfg) } // WithKey 添加單個(gè)鍵. 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() ) // 時(shí)間格式自定義 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)) } // 級(jí)別顯示自定義 cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString(level.String()) } return zapcore.NewJSONEncoder(cfg) } // WithKey 添加單個(gè)鍵. 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
標(biāo)準(zhǔn)輸出方法, 方便直接調(diào)用;
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...)) }
上面的進(jìn)階代碼摘自我開(kāi)發(fā)的一個(gè)git項(xiàng)目中, 主要是一個(gè)Go
的標(biāo)準(zhǔn)項(xiàng)目布局, 封裝了一些常用的組件, 有興趣的朋友可以了解一下, 對(duì)新手還是很友好的;
到此這篇關(guān)于GO語(yǔ)言框架快速集成日志模塊的操作方法的文章就介紹到這了,更多相關(guān)go語(yǔ)言日志模塊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言到底有沒(méi)有引用傳參(對(duì)比 C++ )
這篇文章主要介紹了Go 到底有沒(méi)有引用傳參(對(duì)比 C++ ),需要的朋友可以參考下2017-09-09利用Golang生成整數(shù)隨機(jī)數(shù)方法示例
這篇文章主要介紹了利用Golang生成整數(shù)隨機(jī)數(shù)的相關(guān)資料,文中給出了詳細(xì)的介紹和完整的示例代碼,相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-04-04golang time包下定時(shí)器的實(shí)現(xiàn)方法
定時(shí)器的實(shí)現(xiàn)大家應(yīng)該都遇到過(guò),最近在學(xué)習(xí)golang,所以下面這篇文章主要給大家介紹了關(guān)于golang time包下定時(shí)器的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-12-12Golang unsafe.Sizeof函數(shù)代碼示例使用解析
這篇文章主要為大家介紹了Golang unsafe.Sizeof函數(shù)代碼示例使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12web項(xiàng)目中g(shù)olang性能監(jiān)控解析
這篇文章主要為大家介紹了web項(xiàng)目中g(shù)olang性能監(jiān)控詳細(xì)的解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04