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 .

Related Content


Customizing a type's logging behavior

You may find cases where you wish to control how a value is logged, differently than how it’s used in other contexts. The log/slog package gives you a lot of flexibility in this regard, for custom types: Customizing a type’s logging behavior If a type implements the LogValuer interface, the Value returned from its LogValue method is used for logging. You can use this to control how values of the type appear in logs.


Expanding errors with ReplaceAttr

Let’s look at one other example where I reach for ReplaceAttr regularly: Logging additional error detail. (I talk more about this approach in my Boot.Dev course Learn Logging and Observability in Go (see below for a discount code).) For this technique to be meaningful, I first need errors that contain additional details. And this can take many forms, but probably the most ubiquitous form is an error that contains a stack trace, such as those produced by the popular package github.


Test logging with ReplaceAttr

Today’s topic is probably the most interesting, subtle, and complicated of the three HandlerOptions fields: ReplaceAttr. type HandlerOptions type HandlerOptions struct { … // ReplaceAttr is called to rewrite each non-group attribute before it is logged. // The attribute's value has been resolved (see [Value.Resolve]). // If ReplaceAttr returns a zero Attr, the attribute is discarded. // // The built-in attributes with keys "time", "level", "source", and "msg" // are passed to this function, except that time is omitted // if zero, and source is omitted if AddSource is false.

Get daily content like this in your inbox!

Subscribe