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.