Question: abstracting loggers & other frameworks using interfaces

agolangf · 2017-11-10 11:00:13 · 670 次点击    
这是一个分享于 2017-11-10 11:00:13 的资源,其中的信息可能已经有所发展或是发生改变。

Hey ! Let me first say that I've searched for an answer to my question over this topic, but didn't find anything that answer my questions (If I missed an article or something else that relates to this topic, please send it to me ! :grinning:).

First, a bit of context. I'm in the process of creating some libraries that will help me code other applications in Go. And while doing that, I'm facing a problem: I'd like to use loggers in these libraries, but it is optional. If a user does want log messages, it only has to call a obj.SetLogger(logger pkg.StructuredLoggerInterface). The problem is in the StructuredLoggerInterface interface. I want it to be an interface for Logrus, apex/log and other structured loggers, as I don't want to impose on the user of the library (let's name it A) which logger he should use, as long as it follow a basic contract for a structured logger. The interface would somewhat look like this:

type StructuredLoggerInterface interface{
   Print(...interface{})
   WithField(key string, obj interface{}) StructuredLoggerInterface
}

Problem: It requires a custom implementation for every logger that needs to be used as loggers, as Logrus (or others) actually return internal custom structures (*logrus.Entry for example). I need an custom adapter for every logging library.

What I've done now is creating a small library (Let's name it logpkg), containing the StructuredLoggerInterface and some implementation for different type of Loggers. This library logpkg is then used as dependency by my other projects needing loggers.

Meaning that an external user would need to wrap his logger in one of my custom structures before passing it to my library A, something like:

obj.SetLogger(logpkg.NewLogrusWrapper(myLogrusWrapper))

So, Here is my questions:

  1. As a user of library A, what would be your thoughts on that type of structure ? Would it bother you ? Can I improve this system to make it easier to use ?

  2. Am I going too far ? Is this kind of absraction not needed ? Should I just require Logrus directly ?

  3. In the same situation, what would you do ?

PS: I know that I could just require a simple Logger with

type SimpleLogger interface {
  Print(...interface{})

}

But that doesn't fix the overall issue. There was a point where I asbtracted mgo to be able to provide a custom test implementation, and I still had to provide an adapter for the real mgo. Every time I use mgo, I will have to recreate it, or extract the mgo abstraction & adapter in an external library. Even if I simplify the Logger interfaces, I will have the problem with other packages.


评论:

tylermumford:

I was going to suggest that you simply make a new interface and then add a couple methods to common loggers so they could implement it. I thought method definitions worked the same way as extensions in Swift, allowing you to add methods to types you didn't define.

But alas, I read the Go spec and that is not permitted; methods have to be defined in the same package as their receiver types. Good luck finding another solution. :)

phuber:

As long as you provide an interface that others can implement, provide a default implementation and allow the user to specify their own implementation via dependency injection, I see no issues with your approach.

I believe this is the layer supertype pattern.

Not sure if you can get past the adapter problem, it's required to implement this pattern.

jerf:

I definitely recommend a logging abstraction and not hard-wiring one, because you really limit your appeal if you hardcode what will be "the wrong choice" for 80%+ of your users.

Part of the problem is that the Go library didn't declare a standard, and a part of the problem is that right now it's not even clear what that standard would or should be. (string, ...interface{}) is popular, but it hard codes some expensive operations in to the interface (somehow converting interface{} into strings). And there's an increasing trend towards structured logging, as logrus demonstrates, but it's far from a sure thing. And you simply can't square the circle between those two things, because a struct is not a string, so there's absolutely no way to match it.

One of my libraries provides the ability to pass in a logging function for each of the limited sets of things that the library can log, each of which are passed all the information about the logged incident as a function. A default implementation which uses the built-in logger is provided. This is maximally flexible, but only works because I have two core things that can be logged; obviously this fails for a package with dozens of possible messages. So beyond that, the only thing I've got is, provide yourself an interface that you like and tell people they have to wrap.

And that still leaves you with the decision as to whether you treat log events as objects or strings, for which I've just got nothing good. I mean, I could write things that could deal with it, but they wouldn't be pretty, involving type switches at a minimum and possibly requiring full-on reflection.


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

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