<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在我們的日常開發中, 紀錄檔模組永遠是最基礎且最重要的一個模組, 它可以有效的幫我們發現問題, 定位問題, 最後去解決問題;
zap
是一個可以在go專案中進行快速, 結構化且分級的紀錄檔記錄包, git star數高達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
包還是很靈活的, 基本上設定引數都支援自定義(輸出鍵名, 格式等等), 大家可以按需設定使用;
// 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"` }
專案裡面生產環境為了方便收集紀錄檔可能都比較喜歡用json
, 開發環境中大家又都比較喜歡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 // 保留最大天數 _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 ( 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, // 每個紀錄檔檔案儲存的最大尺寸 單位: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) } // 開啟開發模式,堆疊跟蹤 caller := zap.AddCaller() // 開發模式 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...)) }
上面的進階程式碼摘自我開發的一個git專案中, 主要是一個Go
的標準專案佈局, 封裝了一些常用的元件, 有興趣的朋友可以瞭解一下, 對新手還是很友好的;
到此這篇關於GO語言框架快速整合紀錄檔模組的操作方法的文章就介紹到這了,更多相關go語言紀錄檔模組內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45