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.