Logging common fields

April 16, 2026

It’s common that you’ll want to include certain attributes in all logs in an application or component. log/slog makes this pretty easy.

Overview

Some attributes are common to many log calls. For example, you may wish to include the URL or trace identifier of a server request with all log events arising from the request. Rather than repeat the attribute with every log call, you can use Logger.With to construct a new Logger containing the attributes:

logger2 := logger.With("url", r.URL)

The arguments to With are the same key-value pairs used in Logger.Info. The result is a new Logger with the same handler as the original, but additional attributes that will appear in the output of every call.

Put another way:

logger.Error("oh noes!", "foo", "bar")

and

logger = logger.With("foo", "bar")
logger.Error("oh noes!")

produce the same log output. The difference is, that in the second form, you can re-use the same logger for many logs, without repeating the common attribute:

func run(logger *slog.Logger) {
  logger = logger.With("key", "value")
  doFoo(logger)
  doBar(logger)
}

func doFoo(logger *slog.Logger) {
  /* ... */
  logger.Info("Did foo")
}

func doBar(logger *slog.Logger) {
  /* ... */
  logger.Info("Did bar")
}

This will log:

time=... level=INFO msg="Did foo" key=value
time=... level=INFO msg="Did bar" key=value

Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Performance considerations

You’ve likely wondered why the log/slog package has some odd-looking functions and concepts in some places. Why do you set a handler’s level to a Leveler value, rather than a simple Level? Why so many ways to create key/value pairs ("key", "value" vs "key", slog.AnyValue("value") vs "key", slog.StringValue("value") vs slog.Any("key", "value") vs slog.String("key", "value"))? It mostly comes down to one thing: Performance. Or, more accurately, trying to balance performance with an easy-to-use API.


slog.HandlerOptions

Now we’ll begin looking at HandlerOptions. Despite the Handler-centricity, this is something we care heavily about as mere users of the slog library. Whenever you instantiate either slog.JSONHandler or slog.TextHandler, you have the option of providing a HandlerOptions object, to tweak the default handler behavior. We’ll look at each of the config options, one at a time. type HandlerOptions type HandlerOptions struct { // AddSource causes the handler to compute the source code position // of the log statement and add a SourceKey attribute to the output.


The better test alternative to slog.DiscardHandler

Last time I described slog.DiscardHandler as a viable way to silence logs in tests. Now I want to tell you why I never use that option, and you probably shouldn’t, either. But first a story. The slog package was introduced with Go 1.21 in August of 2023. It didn’t yet have slog.DiscardHandler, so you had to roll your own. But that was easy: logger := slog.New(slog.NewTextHandler(io.Discard, nil)) Easy as it was, it was also annoying.

Get daily content like this in your inbox!

Subscribe