Attribute evaluation

May 25, 2026

log.With isn’t the only trick available for improving performance of logging.

Many values you may want to pass to a logger need to be calculated. And sometimes that calculation is expensive. And if a log is omitted, because it’s a debug log, and our logger is only configured for info-and-up level, that calculation should be skipped.

Performance considerations

The arguments to a log call are always evaluated, even if the log event is discarded. If possible, defer computation so that it happens only if the value is actually logged. For example, consider the call

slog.Info("starting request", "url", r.URL.String())  // may compute String unnecessarily

The URL.String method will be called even if the logger discards Info-level events. Instead, pass the URL directly:

slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed

This trick—passing a fmt.Stringer rather than a literal string—is a good example of the principal of deferring calculation, but it’s not the best example in practice. As the documentation goes on to explain:

The built-in TextHandler will call its String method, but only if the log event is enabled.

But notice: the fmt.Stringer trick depends on which log handler backend you’re using, and whether or not it handles fmt.Stringer values specially.

For JSONHandler or many third-party handlers, this specific trick won’t work. But stay tuned for that one weird trick for all your loggers!


Share this

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

Unsure? Browse the archive .

Related Content


Lazy attribute evaluation for JSONHandler

As I was writing yesterday’s post, a portion of the GoDoc confused me. I’ve now spent over 3 hours with Claude trying to parse the prose grammatically, build test cases, and make general sense of it. I think I finally have… Here’s hoping! So, yesterday we saw how you can lazy-evaluate some values when using TextHandler. But the proposed solution (pass a fmt.Stringer rather than a literal string) has other, likely uninintended, consequences if you’re using JSONHandler:


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.


Working with Records

In my experience, it’s rare you’ll need to worry about slog Records, unless you’re writing a Handler, or some kind of middleware/transform. But even if you never need to manipulate a Record directly, understanding the concept can be useful. Working with Records Sometimes a Handler will need to modify a Record before passing it on to another Handler or backend. A Record contains a mixture of simple public fields (e.g. Time, Level, Message) and hidden fields that refer to state (such as attributes) indirectly.

Get daily content like this in your inbox!

Subscribe