GO语言框架快速集成日志模块的操作方法
来源:脚本之家
时间:2022-12-28 13:43:54 125浏览 收藏
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《GO语言框架快速集成日志模块的操作方法》,聊聊模块、go语言日志,我们一起来看看吧!
zap包的集成
简介
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 }, }, } 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语言日志的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
-
471 收藏
-
312 收藏
-
120 收藏
-
344 收藏
-
202 收藏
-
199 收藏
-
145 收藏
-
168 收藏
-
165 收藏
-
473 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 鲤鱼花生
- 这篇文章内容太及时了,细节满满,受益颇多,收藏了,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-03-22 11:37:13
-
- 机灵的酒窝
- 这篇技术文章真及时,太全面了,很好,码起来,关注博主了!希望博主能多写Golang相关的文章。
- 2023-02-07 14:14:32
-
- 怕孤独的河马
- 太给力了,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢up主分享技术贴!
- 2023-02-02 15:04:34
-
- 自然的大炮
- 这篇文章太及时了,太细致了,很有用,码起来,关注作者了!希望作者能多写Golang相关的文章。
- 2023-02-01 21:41:22
-
- 贤惠的八宝粥
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢老哥分享技术贴!
- 2023-01-26 06:17:09