在GO中构建一个记录纸包装器,并支持多个记录库
#go #logging #wrapper #logrus

构建GO应用程序时,记录是开发过程的重要组成部分。 GO标准库提供了一个基本的记录包,但没有提供许多高级功能。幸运的是,有许多第三方记录库可提供更多功能和灵活性。

但是,在整个代码库中使用不同的日志记录库可能使其难以维护和扩展。那是记录器包装器派上用场的地方。在本文中,我们将讨论如何创建一个可以与不同的记录库一起使用的记录包装。

了解记录器包装器
记录器包装器是您的应用程序代码和基础日志记录库之间的一层。它提供了一个简化的API,该API抽象了不同日志记录库的复杂性,从而更容易在它们之间切换或添加新的日志库。

要创建一个记录器包装器,您需要定义一个界面来描述您应用程序需求的记录方法。例如,基本的记录器接口可以包括信息,警告和错误等方法。然后,您可以创建此接口的实现,该界面使用特定的日志记录库,例如logrus或zap。

定义了接口和实现后,您可以创建一个露出接口方法并允许您在实现之间切换的Logger包装器。这样,您可以在整个代码库中使用相同的日志记录API并更改基础记录库,而无需更改应用程序代码。

github源代码:https://github.com/ansu/multilogger

定义记录器接口
让我们首先定义一个简单的记录器接口,该接口包括我们需要的方法:

type Logger interface {
 Debug(msg string, fields map[string]interface{})
 Info(msg string, fields map[string]interface{})
 Warn(msg string, fields map[string]interface{})
 Error(msg string, fields map[string]interface{})
 Fatal(msg string, fields map[string]interface{})
}

在此接口中,我们有五种方法:调试,信息,警告,错误和致命。这些方法采用格式字符串和一个字段地图。

用logrus实现记录器接口
下一步是使用logrus实现记录器接口。我们将创建一个包含logrus logger实例的logruslogger结构。这是一个例子:

type LogrusLogger struct {
 logger *logrus.Logger
 ctx    context.Context
}

func NewLogrusLogger(loggerType string, ctx context.Context) *LogrusLogger {
 logger := logrus.New()
 logger.Out = os.Stdout

 return &LogrusLogger{logger: logger, ctx: ctx}
}

func (l *LogrusLogger) Debug(msg string, fields map[string]interface{}) {

 l.logger.WithFields(fields).Debug(msg)
}

func (l *LogrusLogger) Info(msg string, fields map[string]interface{}) {
 l.addContextCommonFields(fields)

 l.logger.WithFields(fields).Info(msg)
}

func (l *LogrusLogger) Warn(msg string, fields map[string]interface{}) {
 l.logger.WithFields(fields).Warn(msg)
}

func (l *LogrusLogger) Error(msg string, fields map[string]interface{}) {
 l.logger.WithFields(fields).Error(msg)
}

func (l *LogrusLogger) Fatal(msg string, fields map[string]interface{}) {
 l.logger.WithFields(fields).Fatal(msg)
}

func (l *LogrusLogger) addContextCommonFields(fields map[string]interface{}) {
 if l.ctx != nil {
  for k, v := range l.ctx.Value("commonFields").(map[string]interface{}) {
   if _, ok := fields[k]; !ok {
    fields[k] = v
   }
  }
 }
}


在此示例中,我们创建了一个包装Logrus Logger实例的logruslogger struct。 newlogruslogger()函数创建了一个新的logrus logger实例,我们使用logrus logger实例实现了每种记录方法。

用zap
实现记录器接口

type ZapLogger struct {
 logger *zap.Logger
 ctx    context.Context
}

func NewZapLogger(loggerType string, ctx context.Context) *ZapLogger {
 logger, _ := zap.NewProduction()

 return &ZapLogger{logger: logger, ctx: ctx}
}

func (l *ZapLogger) Debug(msg string, fields map[string]interface{}) {
 l.addContextCommonFields(fields)

 l.logger.Debug("", zap.Any("args", fields))
}

func (l *ZapLogger) Info(msg string, fields map[string]interface{}) {
 l.addContextCommonFields(fields)

 l.logger.Info("", zap.Any("args", fields))
}

func (l *ZapLogger) Warn(msg string, fields map[string]interface{}) {
 l.addContextCommonFields(fields)

 l.logger.Warn("", zap.Any("args", fields))
}

func (l *ZapLogger) Error(msg string, fields map[string]interface{}) {
 l.addContextCommonFields(fields)

 l.logger.Error("", zap.Any("args", fields))
}

func (l *ZapLogger) Fatal(msg string, fields map[string]interface{}) {
 l.addContextCommonFields(fields)

 l.logger.Fatal("", zap.Any("args", fields))
}

func (l *ZapLogger) addContextCommonFields(fields map[string]interface{}) {
 if l.ctx != nil {
  for k, v := range l.ctx.Value("commonFields").(map[string]interface{}) {
   if _, ok := fields[k]; !ok {
    fields[k] = v
   }
  }
 }
}

LoggerWrapper将被应用程序层消费:

type LoggerWrapper struct {
 logger Logger
}

func NewLoggerWrapper(loggerType string, ctx context.Context) *LoggerWrapper {
 var logger Logger

 switch loggerType {
 case "logrus":
  logger = NewLogrusLogger(loggerType, ctx)
 case "zap":
  logger = NewZapLogger(loggerType, ctx)
 default:
  logger = NewLogrusLogger(loggerType, ctx)
 }

 return &LoggerWrapper{logger: logger}
}

func (lw *LoggerWrapper) Debug(msg string, fields map[string]interface{}) {

 lw.logger.Debug(msg, fields)
}

func (lw *LoggerWrapper) Info(msg string, fields map[string]interface{}) {
 lw.logger.Info(msg, fields)
}

func (lw *LoggerWrapper) Warn(msg string, fields map[string]interface{}) {
 lw.logger.Warn(msg, fields)
}

func (lw *LoggerWrapper) Error(msg string, fields map[string]interface{}) {
 lw.logger.Error(msg, fields)
}

func (lw *LoggerWrapper) Fatal(msg string, fields map[string]interface{}) {
 lw.logger.Fatal(msg, fields)
}


Image description

在第一步期间,使用Logrus或ZAP软件包初始化了应用程序。一旦完成初始化,应用程序中使用的所有日志都将通过相应的软件包。例如,如果使用LOGRUS初始化了应用程序,则将通过LOGRUS软件包处理所有日志。同样,如果使用ZAP初始化了应用程序,则所有日志都将通过ZAP软件包。

您可能会很好奇为什么我使用上下文引用以及我们可以从中获得什么优势。
要在每个日志中应包含的日志常见属性,您可以使用包含常见属性的上下文对象。common属性通常在每个日志输入中记录的common属性包括:

请求跟踪信息,例如请求ID,ALB ID(如果存在),以及用于微服务通信的跨度ID。此信息可用于将跨多个服务的日志相关联并跟踪流程通过系统的请求。

我们可以使用中间件自动将公共字段添加到每个日志消息中,而无需将上下文对象明确传递到每个日志记录方法。MiddleWare函数在上下文中设置了Logger库可以直接消耗的上下文中的公共字段。

总而言之,我们建立了一个支持Logrus和ZAP记录器的多材料软件包。您可以从这里下载源代码:MultiLogger github repo

我们已经演示了如何使用Logger包装器与字段记录消息,以及如何使用上下文对象将公共字段添加到每个日志中。通过使用Logger包装器,我们可以在不更改应用程序代码的情况下轻松切换不同的记录框架。

总的来说,多木制软件包提供了一种简单而灵活的方法来处理GO应用程序的登录。

**希望您发现这篇文章很有帮助。如果您喜欢阅读本文,请喜欢这篇文章,并考虑关注我以获取更多编程教程。

**