The package level logger anti pattern
Dave Cheney · · 957 次点击 · · 开始浏览This post is a spin-off from various conversations around improving (I’m trying not to say standardising, otherwise I’ll have to link to XKCD) the way logging is performed in Go projects.
Consider this familiar pattern for establishing a package level log variable.
package foo
import "mylogger"
var log = mylogger.GetLogger("github.com/project/foo")
What’s wrong with this pattern?
The first problem with declaring a package level log variable is the tight coupling between package foo and package mylogger. Package foo now depends directly on package mylogger at compile time.
The second problem is the tight coupling between package foo and package mylogger is transitive. Any package that consumes package foo is itself dependant on mylogger at compile time.
This leads to a third problem, Go projects composed of packages using multiple logging
libraries, or fiefdoms of projects who can only consume packages that use their particular logging library.
Avoid source level coupling
The solution to this anti pattern is to delay the binding between the type that does the logging, and the type that needs to log, until it is needed. That is, until the variable is declared.
package foo
import "github.com/pkg/log"
type T struct {
logger log.Logger
// other fields
}
Now, the consumer of type T supplies a value of type log.Logger when constructing new T‘s, and the methods on T use the logger they were provided when they want to log.
Interfaces to the rescue
The eagle eyed reader will note that the previous selection removed the package level log variable, but the coupling between package foo and package log remains.
However, this can be remedied by the consumer of the logger type declaring its own interface for the behaviour it expects.
package foo
type logger interface {
Printf(string, ...interface{})
}
type T struct {
logger
// other fields
}
As long as the type assigned to foo.T.logger implements foo.logger the decision for which specific type to use can be deferred until run time in the same way that io.Copy escapes any knowledge of the io.Reader and io.Writer implementations in use until it is invoked.
It’s not just logging
Logging is a cross cutting concern, but the anti patterns associated with it also apply to other common areas like metrics, telemetry, and auditing.
Get involved
The Go 1.9 development window is opening next month. If this topic is important to you, get involved.
Related posts:
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
This post is a spin-off from various conversations around improving (I’m trying not to say standardising, otherwise I’ll have to link to XKCD) the way logging is performed in Go projects.
Consider this familiar pattern for establishing a package level log variable.
package foo
import "mylogger"
var log = mylogger.GetLogger("github.com/project/foo")
What’s wrong with this pattern?
The first problem with declaring a package level log variable is the tight coupling between package foo and package mylogger. Package foo now depends directly on package mylogger at compile time.
The second problem is the tight coupling between package foo and package mylogger is transitive. Any package that consumes package foo is itself dependant on mylogger at compile time.
This leads to a third problem, Go projects composed of packages using multiple logging
libraries, or fiefdoms of projects who can only consume packages that use their particular logging library.
Avoid source level coupling
The solution to this anti pattern is to delay the binding between the type that does the logging, and the type that needs to log, until it is needed. That is, until the variable is declared.
package foo
import "github.com/pkg/log"
type T struct {
logger log.Logger
// other fields
}
Now, the consumer of type T supplies a value of type log.Logger when constructing new T‘s, and the methods on T use the logger they were provided when they want to log.
Interfaces to the rescue
The eagle eyed reader will note that the previous selection removed the package level log variable, but the coupling between package foo and package log remains.
However, this can be remedied by the consumer of the logger type declaring its own interface for the behaviour it expects.
package foo
type logger interface {
Printf(string, ...interface{})
}
type T struct {
logger
// other fields
}
As long as the type assigned to foo.T.logger implements foo.logger the decision for which specific type to use can be deferred until run time in the same way that io.Copy escapes any knowledge of the io.Reader and io.Writer implementations in use until it is invoked.
It’s not just logging
Logging is a cross cutting concern, but the anti patterns associated with it also apply to other common areas like metrics, telemetry, and auditing.
Get involved
The Go 1.9 development window is opening next month. If this topic is important to you, get involved.