Go 日志组件封装

uuid · · 1968 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

## 背景 Go目前流行多个日志组件:logrus、zap、zerolog、seelog,他们有各自的优点,但是使用方式存在差异, 如何屏蔽这些差异,对外提供统一的接口,减少对具体的日志组件依赖,从而方便后续组件替换? 对此,我们可以增加一层防腐层,定义一套统一的日志接口,业务代码不直接依赖具体的日志实现,而是依赖抽象接口,这样就可以将具体的实现进行隔离,业务代码就不会包含具体日志组件相关的代码,当日志组件需要替换时,只要做一层适配,实现约定好的日志接口方法,就可以无缝进行切换日志组件。 ## 方案 ### 定义日志接口 我们需要预先定义好一套完整的日志接口: log/log.go ```go package log type Fields map[string]interface{} type Logger interface { WithField(key string, value interface{}) Logger WithFields(fields Fields) Logger Trace(args ...interface{}) Tracef(format string, args ...interface{}) Debug(args ...interface{}) Debugf(format string, args ...interface{}) Info(args ...interface{}) Infof(format string, args ...interface{}) Warn(args ...interface{}) Warnf(format string, args ...interface{}) Error(args ...interface{}) Errorf(format string, args ...interface{}) Panic(args ...interface{}) Panicf(format string, args ...interface{}) Fatal(args ...interface{}) Fatalf(format string, args ...interface{}) } ``` 接口包含日常使用的所有方法,接口没有任何的外部依赖。 接口定义放在独立的log包下,不需要担心包名和原来的日志冲突,因为到时会替换所有的日志组件 **定义全局日志** 日志组件一般会有一个全局默认的实例,使用的时候直接调用包下对应的函数,例如: ```go log.Info("hello world") log.Error("hello world") ``` 所以我们同样定义一套包级别的导出方法: log/exported.go ```go package log var std Logger // 标准输出 func WithField(key string, value interface{}) Logger { return std.WithField(key, value) } func WithFields(fields Fields) Logger { return std.WithFields(fields) } func Trace(args ...interface{}) { std.Trace(args...) } func Tracef(format string, args ...interface{}) { std.Tracef(format, args...) } func Debug(args ...interface{}) { std.Debug(args...) } func Debugf(format string, args ...interface{}) { std.Debugf(format, args...) } func Info(args ...interface{}) { std.Info(args...) } func Infof(format string, args ...interface{}) { std.Infof(format, args...) } func Warn(args ...interface{}) { std.Warn(args...) } func Warnf(format string, args ...interface{}) { std.Warnf(format, args...) } func Error(args ...interface{}) { std.Error(args...) } func Errorf(format string, args ...interface{}) { std.Errorf(format, args...) } func Panic(args ...interface{}) { std.Panic(args...) } func Panicf(format string, args ...interface{}) { std.Panicf(format, args...) } func Fatal(args ...interface{}) { std.Fatal(args...) } func Fatalf(format string, args ...interface{}) { std.Fatalf(format, args...) } // 初始化全局日志 func InitGlobal(s Logger) { std = s } ``` 注意: - 全局日志使用前需要调用初始化方法:InitGlobal(s Logger) - exported.go同样也是不依赖具体实现,只依赖接口Logger ### 适配Logrus组件 如果项目要使用Logrus时,我们可以对Logrus做一层适配,实现我们的定义好的接口: log/logrus.go ``` /** * @Title Logrus适配 * @Description * @Author hyman * @Date 2021-10-26 **/ package log import ( "github.com/sirupsen/logrus" ) type logrusAdapt struct { l *logrus.Logger } func (s logrusAdapt) WithField(key string, value interface{}) Logger { return newFieldAdapt(s.l.WithField(key, value)) } func (s logrusAdapt) Tracef(format string, args ...interface{}) { s.l.Tracef(format, args...) } func (s logrusAdapt) Debugf(format string, args ...interface{}) { s.l.Debugf(format, args...) } func (s logrusAdapt) Infof(format string, args ...interface{}) { s.l.Infof(format, args...) } func (s logrusAdapt) Warnf(format string, args ...interface{}) { s.l.Warnf(format, args...) } func (s logrusAdapt) Errorf(format string, args ...interface{}) { s.l.Errorf(format, args...) } func (s logrusAdapt) Panicf(format string, args ...interface{}) { s.l.Panicf(format, args...) } func (s logrusAdapt) Fatalf(format string, args ...interface{}) { s.l.Fatalf(format, args...) } func (s logrusAdapt) WithFields(fields Fields) Logger { return newFieldAdapt(s.l.WithFields(logrus.Fields(fields))) } func (s logrusAdapt) Trace(args ...interface{}) { s.l.Trace(args...) } func (s logrusAdapt) Debug(args ...interface{}) { s.l.Debug(args...) } func (s logrusAdapt) Print(args ...interface{}) { s.l.Print(args...) } func (s logrusAdapt) Info(args ...interface{}) { s.l.Info(args...) } func (s logrusAdapt) Warn(args ...interface{}) { s.l.Warn(args...) } func (s logrusAdapt) Error(args ...interface{}) { s.l.Error(args...) } func (s logrusAdapt) Panic(args ...interface{}) { s.l.Panic(args...) } func (s logrusAdapt) Fatal(args ...interface{}) { s.l.Fatal(args...) } // 封装logrus.Entry type fieldAdapt struct { e *logrus.Entry } func (f fieldAdapt) WithField(key string, value interface{}) Logger { return newFieldAdapt(f.e.WithField(key, value)) } func (f fieldAdapt) WithFields(fields Fields) Logger { return newFieldAdapt(f.e.WithFields(logrus.Fields(fields))) } func (f fieldAdapt) Tracef(format string, args ...interface{}) { panic("implement me") } func (f fieldAdapt) WithError(err error) Logger { return newFieldAdapt(f.e.WithError(err)) } func (f fieldAdapt) Debugf(format string, args ...interface{}) { f.e.Debugf(format, args...) } func (f fieldAdapt) Infof(format string, args ...interface{}) { f.e.Infof(format, args...) } func (f fieldAdapt) Printf(format string, args ...interface{}) { f.e.Printf(format, args...) } func (f fieldAdapt) Warnf(format string, args ...interface{}) { f.e.Warnf(format, args...) } func (f fieldAdapt) Warningf(format string, args ...interface{}) { f.e.Warningf(format, args...) } func (f fieldAdapt) Errorf(format string, args ...interface{}) { f.e.Errorf(format, args...) } func (f fieldAdapt) Fatalf(format string, args ...interface{}) { f.e.Fatalf(format, args...) } func (f fieldAdapt) Panicf(format string, args ...interface{}) { f.e.Panicf(format, args...) } func (f fieldAdapt) Debug(args ...interface{}) { f.e.Debug(args...) } func (f fieldAdapt) Info(args ...interface{}) { f.e.Info(args...) } func (f fieldAdapt) Print(args ...interface{}) { f.e.Print(args...) } func (f fieldAdapt) Warn(args ...interface{}) { f.e.Warn(args...) } func (f fieldAdapt) Warning(args ...interface{}) { f.e.Warning(args...) } func (f fieldAdapt) Error(args ...interface{}) { f.e.Error(args...) } func (f fieldAdapt) Fatal(args ...interface{}) { f.e.Fatal(args...) } func (f fieldAdapt) Panic(args ...interface{}) { f.e.Panic(args...) } func (f fieldAdapt) Debugln(args ...interface{}) { f.e.Debugln(args...) } func (f fieldAdapt) Infoln(args ...interface{}) { f.e.Infoln(args...) } func (f fieldAdapt) Println(args ...interface{}) { f.e.Println(args...) } func (f fieldAdapt) Warnln(args ...interface{}) { f.e.Warnln(args...) } func (f fieldAdapt) Warningln(args ...interface{}) { f.e.Warningln(args...) } func (f fieldAdapt) Errorln(args ...interface{}) { f.e.Errorln(args...) } func (f fieldAdapt) Fatalln(args ...interface{}) { f.e.Fatalln(args...) } func (f fieldAdapt) Panicln(args ...interface{}) { f.e.Panicln(args...) } func (f fieldAdapt) Trace(args ...interface{}) { f.e.Trace(args...) } func newFieldAdapt(e *logrus.Entry) Logger { return fieldAdapt{e} } func NewLogrusAdapt(l *logrus.Logger) Logger { return &logrusAdapt{ l: l, } } ``` 适配器只是对logrus实例进行轻量级封装,采用组合方式,不会破坏原有逻辑。 logrus的WithField功能比较特别,返回的是logrus.Entry对象,所以还需要对logrus.Entry进行适配:fieldAdapt ### 项目使用 封装好后,要如何使用? 首先一般我们是在main.go中实例化我们的日志对象 ```go import ( "github.com/sirupsen/logrus" "log/log" ) // 全局日志实例 log.InitGlobal(log.NewLogrusAdapt(logrus.StandardLogger())) // 初始化 // 自定义日志实例 var customLog = log.NewLogrusAdapt(logrus.New()) ``` 我们只是对组件进行适配,组件的配置还是和原来保持一样,例如:日志输出,级别等 ```go logrusLog := logrus.New() logrusLog.SetLevel(logrus.InfoLevel) src, err := os.OpenFile(os.DevNull, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { panic(err) } writer := bufio.NewWriter(src) logrusLog.SetOutput(writer) customLog:=log.NewLogrusAdapt(logrusLog) ``` 注:一般情况下项目只有main.go才会和具体日志组件有耦合,其他地方只会和logger接口进行关联,后续需要替换日志组件,只需要修改main.go。 **全局日志调用** ```go import ( "log/log" ) func Hello() { log.Info("hellow world") // 调用 } ``` **自定义日志调用** 一般通过注入方式,把具体日志实例注入到模块中,然后在模块中使用: main.go ``` customLog:=log.NewLogrusAdapt(logrusLog) business:= NewBusiness(customLog) ``` business.go ```go import ( "log/log" ) type Business struct { log log.Logger } func (r *Business) SayHello() { r.log.Info("say hello") } func NewBusiness(log log.Logger) *Business { return &Business{log: log} } ``` ### 组件替换 后续如果需要替换日志组件只需要增加对应的适配器,实现指定的接口,然后修改main.go中的实例方法就可以达到日志替换的目的。 原文地址:[Go 日志组件封装 | 艺术码农的小栈](https://itart.cn/blogs/2021/practice/go-log-wrap.html)

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1968 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传