blog

12021-12-17

SEAN K.H. LIAO

loggers

Loggers, usually you don't want the global one as there's groups of settings you want to pass together or log everytime. So now you have a logger instance, how do you pass that downstream?

One option is to make the logger part of struct, after all, you probably also have other state you need to hold:

func (s *Server) DoX() {
        s.l.Info("something happened")
}

Another is to stuff the logger into a context and pass it down the request chain:

func (s *Server) DoX(ctx context.Context) {
        l := logr.FromContextOrDiscard(ctx)
        l.Info("something happened")
}

ctx

For the past year, I've been using logr as my logging interface and extensively using its FromContextOrDiscard to pass around the logger.

Advantages:

Disadvantages:

logger in struct

I've now more or less swung back to having the logger embedded as part of a struct. It is unfortunate that now free standing functions can't easily log, but maybe that's ok? The logger provenance is much clearer, but embedding fixed values for the duration of a request becomes more awkward.

In this light, zerolog's Context makes more sense. Maybe it's something I'll look at.

trace

An alternative to repeating fields every time you log may be to log trace_id / span_id fields with every request, and you'd look at all messages to get the full picture, though how well this works with levelled logging might be questionable, if key information is only included in the more verbose log levels.