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:
1func (s *Server) DoX() {
2 s.l.Info("something happened")
3}
Another is to stuff the logger into a context and pass it down the request chain:
1func (s *Server) DoX(ctx context.Context) {
2 l := logr.FromContextOrDiscard(ctx)
3 l.Info("something happened")
4}
For the past year,
I've been using logr
as my logging interface
and extensively using its FromContextOrDiscard
to pass around the logger.
Advantages:
context.Context
Disadvantages:
l := logr.FromContextOrDiscard(ctx)
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.
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.