## 背景
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)
有疑问加站长微信联系(非本文作者))