日志存储库的抽象实现



我想wrap / abstract日志 API(隐藏实现)并使用一些库 我们想要隐藏实现的原因是我们想提供我们的日志 API 并隐藏在 hod 下使用的记录器库,现在它的[logrus][1],它也可以zapklog,使用日志 api 的人不需要更改他的日志代码用法当我切换到不同的记录器实现时(我们只是在更改引擎......

我所做的是创建结构,init logger返回我的结构,此外还创建包装功能的函数(见下文),

package logger
import (
"fmt"
"os"
"github.com/sirupsen/logrus"
)
const (
AppLogLevel = "APP_LOG"
defLevel    = "error"
)
type Logger struct {
label      string
version    string
loggerImpl *logrus.Logger
}
// init logger
func NewLogger(level string,version string) *Logger {
lvl := logLevel(level)
logger := &logrus.Logger{
Out:       os.Stdout,
Level:     lvl,
Formatter: &logrus.TextFormatter{},
}
return &Logger{
version: version,
loggerImpl: logger,
}
}
// GetLogLevel - Get level from env
func getLogLevel() string {
lvl, _ := os.LookupEnv(AppLogLevel)
if lvl != "" {
return lvl
}
return defLevel
}
func logLevel(lvl string) logrus.Level {
switch lvl {
case "debug":
return logrus.DebugLevel
case "info":
return logrus.InfoLevel
case "error":
return logrus.ErrorLevel
case "warn":
return logrus.WarnLevel
case "fatal":
return logrus.FatalLevel
case "panic":
return logrus.PanicLevel
default:
panic(fmt.Sprintf("the specified %s log level is not supported", lvl))
}
}
func (logger *Logger) SetLevel(level string) {
lvl := logLevel(level)
logger.loggerImpl.SetLevel(lvl)
}

func (logger *Logger) Debugf(format string, args ...interface{}) {
logger.loggerImpl.Debugf(format, args...)
}
func (logger *Logger) Infof(format string, args ...interface{}) {
logger.loggerImpl.Infof(format, args...)
}

func (logger *Logger) Errorf(format string, args ...interface{}) {
logger.loggerImpl.Errorf(format, args...)
}
func (logger *Logger) Fatalf(format string, args ...interface{}) {
logger.loggerImpl.Fatalf(format, args...)
}
func (logger *Logger) Panicf(format string, args ...interface{}) {
logger.loggerImpl.Panicf(format, args...)
}
func (logger *Logger) Debug(args ...interface{}) {
logger.loggerImpl.Debug(args...)
}
func (logger *Logger) Info(args ...interface{}) {
logger.loggerImpl.Info(args...)
}
func (logger *Logger) Warn(args ...interface{}) {
logger.loggerImpl.Warn(args...)
}
func (logger *Logger) Error(args ...interface{}) {
logger.loggerImpl.Error(args...)
}
func (logger *Logger) Fatal(args ...interface{}) {
logger.loggerImpl.Fatal(args...)
}
func (logger *Logger) Panic(args ...interface{}) {
logger.loggerImpl.Panic(args...)
}

我错过了什么吗?就像我尝试将其更改为 zap 时一样(将结构更改为以下内容:

type Logger struct {
label      string
version    string
loggerImpl *zap.Logger
}

此代码不起作用(所有适用于logrus的函数代码)

logger.loggerImpl.SetLevel(lvl)

还有

logger.loggerImpl.Tracef(format, args...)

等等,因为zaplib 没有它们,知道如何抽象它,将来可以同时支持两者或更多?

更新

我尝试使用以下(适配器模式):(但看起来在我现在有递归调用的方法中)知道如何避免它吗?

package logger
import (
log "github.com/sirupsen/logrus"
)
type Logger struct {
adapter Adapter
}
func (l *Logger) SetLogger(a Adapter) {
l.adapter = a
}
func (l *Logger) Debugf(fmt string, args ...interface{}) {
l.adapter.Debugf(fmt, args...)
}
type Adapter interface {
SetLevel(level string)
Tracef(format string, args ...interface{})
Debugf(string, ...interface{})
Infof(format string, args ...interface{})
Warnf(format string, args ...interface{})
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
Panicf(format string, args ...interface{})
Trace(args ...interface{})
Debug(args ...interface{})
Info(args ...interface{})
Warn(args ...interface{})
Error(args ...interface{})
Fatal(args ...interface{})
}
type StdLoggerAdapter struct {
}
func (l StdLoggerAdapter) SetLevel(level string) {
lvl := logLevel(level)
l.SetLevel(string(lvl))
}
func (l StdLoggerAdapter) Tracef(format string, args ...interface{}) {
l.Tracef(format, args...)
}
func (l StdLoggerAdapter) Infof(format string, args ...interface{}) {
l.Infof(format,args)
}
func (l StdLoggerAdapter) Warnf(format string, args ...interface{}) {
l.Warnf(format,args)
}
...
func (l StdLoggerAdapter) Debugf(fmt string, args ...interface{}) {
log.Printf(fmt, args...)
}
func NewLogger(a Adapter) Logger {
return Logger{adapter: a}
}
func main() {
logger := NewLogger(StdLoggerAdapter{})
logger.Debugf("stdlib logger debug msg")
}
func logLevel(lvl string) log.Level {
var level log.Level
switch lvl {
//case "trace":
//  level = log.TraceLevel
case "debug":
level = log.DebugLevel
case "info":
level = log.InfoLevel
case "warn":
level = log.WarnLevel
case "error":
level = log.ErrorLevel
case "fatal":
level = log.FatalLevel
case "panic":
level = log.PanicLevel
default:
level = log.ErrorLevel
}
return level
}

建议以不同的方式思考问题,并完全避免类型/界面恶作剧。使用您喜欢的 API 创建一个无状态记录器,并且跨不同的记录器实现感觉合理。如果要更新基础记录器,请更新它,用户只需更新其依赖项即可查看它。如果要保持分离,可以通过导入路径隔离记录器后端。

https://gitlab.com/tight5/kit/blob/master/logger/logger.go 遵循这一点,并可能激发你自己的目的。它主要基于 go-kit 的记录器,我们一开始就喜欢它,并希望能够根据需要将其放入仪器中。它还重定向了 stdlib 日志包,我们非常重视它,因此用户不必将所有现有的日志语句更新到我们的库中。

package main
import (
"gitlab.com/tight5/kit/logger"
"log"
)
func main() {
logger.Info().Log("msg", "this is a logging example")
log.Println("also logs through kit logger")
}

https://play.golang.org/p/6sBfI85Yx6g

go-kit的日志界面功能强大,我会在任何家庭成长之前对其进行评估:https://github.com/go-kit/kit/blob/master/log/log.go 只是func Log(keyvals ...interface{}),现在支持zaplogrus后端。


https://github.com/go-kit/kit/tree/master/log/logrus https://github.com/go-kit/kit/tree/master/log/zap

例如,在链接的包级记录器中,更改用户的后端就像更改默认值一样简单: https://gitlab.com/tight5/kit/blob/master/logger/logger.go#L42-53(我们在抽象中称之为格式,但您实际上只是在选择记录器实现)

以下是上面的摘录,展示了logrus的示例实现(zap将非常相似)。

... other imports ... 
import kitlogrus "github.com/go-kit/kit/log/logrus"
import "github.com/sirupsen/logrus"
func Init(format LogFormat, logLevel LogLevel) {
var l log.Logger
switch format {
case FormatJson:
l = log.NewJSONLogger(os.Stdout)
case FormatLogfmt:
l = log.NewLogfmtLogger(os.Stdout)
case FormatNop:
l = log.NewNopLogger()
case FormatLogrus:
l = kitlogrus.NewLogrusLogger(logrus.New())
case FormatZap:
default:
panic(fmt.Errorf("invalid log format: %v", format))
}
...
}

希望这能激发你自己实现的想法!

我创建了这个存储库供个人使用,我认为可以对其进行改进以满足您的目的。

你可以看看。

PS:在添加新记录器时,例如(零日志),您可以根据需要更改变量logger的值并更改方法(Info(args ...)Debug(args ...)等)。

日志包装器

您可以使用适配器:

package main
import (
"log"
"github.com/sirupsen/logrus"
)
type Logger struct {
adapter Adapter
}
func (l *Logger) SetLogger(a Adapter) {
l.adapter=a
}
func (l *Logger) Debugf(fmt string,args...interface{}) {
l.adapter.Debugf(fmt,args...)
}
type Adapter interface {
Debugf(string,...interface{})
}
type StdLoggerAdapter struct {}
func (l StdLoggerAdapter) Debugf(fmt string,args...interface{}) {
log.Printf(fmt,args...)
}
type LogrusAdapter struct {}
func (l LogrusAdapter) Debugf(fmt string,args...interface{}) {
logrus.Debugf(fmt,args...)
}

func NewLogger(a Adapter) Logger {
return Logger{adapter:a}
}

func main() {
logger:=NewLogger(StdLoggerAdapter{})
logger.Debugf("stdlib logger debug msg")
logger.SetLogger(LogrusAdapter{})
logger.Debugf("logrus debug msg")
}

说实话,我对你想做的事情有点困惑。

一般来说,要回答您的问题的实质

若要能够在代码中的不同日志记录库之间切换,必须定义一个特定的接口,然后为每个库实现该接口。由于您无法在另一个包中的结构上实现方法,因此您必须包装其他库并在包装器上定义方法。

您的示例代码将"level"作为记录器的属性;我猜您希望您的记录器决定您想要的日志记录级别,并且只需将库记录器用作泵送消息的管道。

因此,让我们假设一个简化版本:

type LogLevel int
const (
LevelInfo LogLevel = iota
LevelDebug
LevelWarn
LevelError
LevelFatal
)
type interface ILogger {
Log(args ...interface{})
}
type struct Logger {
Level LogLevel
internal ILogger
}

这将是其他一切的基础。

有一个问题值得暂停:

不同的记录器是否提供兼容的接口?如果您在"zap"和"logrus"之间切换,是因为您可能实际上想使用它们的特定接口吗?也许它们提供了您真正想要的一些更专业的功能。

如果将它们隐藏在通用接口(如此处ILogger)后面,您将失去这些记录器在实际日志记录方面提供的任何好处。

无论如何,我们将继续,忽略这个问题,让我们看看如何使用这些原语:

func NewLogger(internal ILogger) *Logger {
return &Logger{
Level: LeveLInfo,
internal: internal,
}
}
func (logger *Logger) Log(level LogLevel, args ...interface{}) {
if level >= logger.Level {
logger.internal.Log(args...)
}
}
func (logger *Logger) Logf(level LogLevel, fmt string, args ...interface{}) {
if level >= logger.Level {
msg := fmt.Sprintf(fmt, args...)
logger.internal.Log(msg)
}
}  

现在,您可以将InfoInfof以及DebugDebugf..等作为简单的便利方法实现。

下面是一个示例。其余的将留给读者作为练习。

func (logger *Logger) Infof(format string, args ...interface{}) {
logger.Logf(LevelInfo, format, args...)
}
func (logger *Logger) Info(args ...interface{}) {
logger.Log(LevelInfo, args...)
}

现在困难的部分:强制所有第三方库符合您的接口。

我不熟悉所有不同的日志记录库,所以这可能证明不是那么简单。您可能需要更改ILogger接口的设计以使其更可行。

无论如何,您通常会这样做:

type ZapLogger *zap.Logger
func (z ZapLogger) Log(args ...interface{}) {
zz := *zap.Logger(z)
// TODO: do something to pump `args` into `zz`
}
type LogrusLogger *logrus.Logger
func (g LogrusLogger) Log(args ...interface{}) {
gg := *logrus.Logger(g)
// TODO: do something to pump `args` into `gg`
}

现在,您拥有了LogrusLoggerZapLogger等类型,它们实现了ILogger,并且可以轻松地与基础日志记录库来回转换,无需任何成本。

因此,您可以实例化自己的记录器来包装底层第三方记录器

var underlying *logrus.Logger = MakeMyLogrusLogger(...)
var myLogger = NewLogger(LogrusLogger(underlying))

现在每个人都可以打电话给myLogger.Infof(....)来记录内容。

如果你决定切换到zap或其他什么,你会改变上面的行(你还必须为zap定义ILogger接口的实现)

var underlying *zap.Logger = MakeMyZapLogger(...)
var myLogger = NewLogger(ZapLogger(underlying))

综上所述,我认为这整个努力是徒劳的,并不值得。由于这些库似乎不提供兼容的接口,并且使用一个库而不是另一个库的全部意义在于您喜欢另一个库为您提供的不同接口。

通过创建接口来抽象出所需的方法或常用方法,并实现如下接口:

type Logger interface {
SetLevel(level string)
Errorf(format string, args ...interface{})
}
type LogrusLogger struct {
label      string
version    string
loggerImpl *logrus.Logger
}
type zapLogger struct {
label      string
version    string
loggerImpl *logrus.Logger
}

根据需要初始化日志:

Logger log := new LogrusLogger{}

Logger log := new ZapLogger{}

将其用作:

log.Errorf("message")

我遇到了同样的问题,我必须创建一个可供多个微服务使用的库,并且需要一些HTTP日志记录。我没有让所有微服务都使用一个日志库,而是必须允许 logrus 或 zap。

我创建了一个具有两种实现(logrus和zap)的记录器接口。

最新更新