Back after an unannounced absence

June 16, 2026

Hey everyone… I dropped the ball! A combination of unexpected family events, prepping for a conference, and some travel, meant I haven’t been writing for much longer than I like. But I’m back!

So where were we? Oh that’s right… performance considerations with log/slog. We had looked at using the fmt.Stringer interface to avoid eager processing with slog.TextHandler. But let’s now look at a more general solution:

Performance considerations

You can also use the LogValuer interface to avoid unnecessary work in disabled log calls. Say you need to log some expensive value:

slog.Debug("frobbing", "value", computeExpensiveValue(arg))

Even if this line is disabled, computeExpensiveValue will be called. To avoid that, define a type implementing LogValuer:

type expensive struct { arg int }

func (e expensive) LogValue() slog.Value {
    return slog.AnyValue(computeExpensiveValue(e.arg))
}

Then use a value of that type in log calls:

slog.Debug("frobbing", "value", expensive{arg})

Now computeExpensiveValue will only be called when the line is enabled.

Let’s adapt the earlier url.URL example to use slog.LogValuer, for use with all handlers:

type myURL struct {
  url.URL
}

func (u myURL) LogValue() slog.Value {
  return slog.StringValue(u.URL.String())
}

This does, of course, mean you need to convert to your custom type before logging:

slog.Info("starting request", "url", myURL{*r.URL})

Share this

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

Unsure? Browse the archive .

Get daily content like this in your inbox!

Subscribe