Customizing a type's logging behavior

May 12, 2026

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. For example, you can redact secret information like passwords, or gather a struct’s fields in a Group. See the examples under LogValuer for details.

A LogValue method may return a Value that itself implements LogValuer. The Value.Resolve method handles these cases carefully, avoiding infinite loops and unbounded recursion. Handler authors and others may wish to use Value.Resolve instead of calling LogValue directly.

We’ll look at specific examples, when we get to the LogValuer part of the documentation, but for now, a few examples where this could be useful:

  • Secret redaction (as mentioned in the quote above)
  • Defer expensive operations until actually used (LogValuer isn’t evaluated for logs that aren’t actually produced)
  • Any time you want the log output to be different than other output

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.


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.


Wrapping output methods

Let’s talk about a feature I’ve never used, or even knew existed… I mentioned a while back that the AddSource field of HandlerOptions controls whether the log output includes the source code position of the log call. But what if that log call is wrapped by a helper, obscuring the meaningful source position? Wrapping output methods The logger functions use reflection over the call stack to find the file name and line number of the logging call within the application.

Get daily content like this in your inbox!

Subscribe