Owner of Logging Context
There’s the late-night dilemma…
Who should be in charge of logging context: a component’s owner or the component itself?
type Logger interface {
With(...kvpairs) Logger
}
type Storage struct {
logger Logger
}
// OPTION 1: component's owner defines the context of component's logger
func main() {
_ = NewStorage(logger.With("component", "storage"))
}
// OPTION 2: component itself is in charge of its logging context
func NewStorage(logger Logger) (st *Storage) {
return &Storage{
logger: logger.With("component", "storage"),
}
}
Fun fact: a couple months back, we ruined the team’s Friday, by debating about a similar topic in the context of (Graphite) metrics namespaces. It has become even more intricate since then :/
Update (2020-04-15)
Many people on Twitter suggest that Option 1 is an obvious choice because only application knows how to name the components. I totally agree with that.
As I wrote later, the real dilemma is not about “application and component” but about “owner of the component”. Function main
, in the example above, was a silly example, that tried (and failed) to illustrate the question in a code.
Let’s try another (silly) example:
// there are buch of different handlers (maybe ten) in this application
type Handler1 struct { logger Logger }
func (h *Handler1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// OPTION 1
req := NewRequst(h.logger.With("component", "request"), r)
}
type Handler2 struct { logger Logger }
func (h *Handler2) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// OPTION 1, still
req := NewRequst(h.logger.With("component", "request"), r)
}
type Request struct {
logger Logger
}
// OPTION 2
func NewRequst(logger Logger, *r http.Request) *Request {
return &Request{
logger: logger.With("component", "request"),
}
}
We want to have a consistent nomenclature across the application’s logs.
Is the choice still obvious? ;)
Do you have an opinion? Share it with me on Twitter.