Hacker News new | ask | show | jobs
by aleksi 275 days ago
My biggest gripe with slog is that there is no clear guidance on supported types of attributes.

One could argue that supported types are the ones provided by Attr "construct" functions (like slog.String, slog.Duration, etc), but it is not enough. For example, there is no function for int32 – does it mean it is not supported? Then there is slog.Any and some support in some handlers for error and fmt.Stringer interfaces. The end result is a bit of a mess.

1 comments

All values are supported.
Well, is fmt.Stringer supported? The result might surprise you:

  req := expvar.NewInt("requests")
  req.Add(1)
  
  attr := slog.Any("requests", req)
  
  slog.New(slog.NewTextHandler(os.Stderr, nil)).Info("text", attr)
  slog.New(slog.NewJSONHandler(os.Stderr, nil)).Info("json", attr)
This code produces

  time=2025-09-12T13:15:42.125+02:00 level=INFO msg=text requests=1
  {"time":"2025-09-12T13:15:42.125555+02:00","level":"INFO","msg":"json","requests":{}}
So the code that uses slog but does not know what handler will be used can't rely on it lazily calling the `String() string` method: half of the standard handlers do that, half don't.
If you need more control, you can create a wrapper type that implements `slog.LogValuer`

    type StringerValue struct {
        fmt.Stringer
    }

    func (v StringerValue) LogValue() slog.Value {
        return slog.StringValue(v.String())
    }

Usage example:

    slog.Any("requests", StringerValue{req})

There might be a case for making the expvar types implement `slog.LogValuer` directly.
So clearly not all values are supported.

And I know that I can create a wrapper for unsupported types. My problem is exactly that – I don't know what types are supported. Is error supported, for example? Should I create a wrapper for it? And, as a handler author, should I support it directly or not?

Not sure what your definition of "supported" is, but I'm afraid you're going to have to bite the bullet and ... gasp ... read the documentation https://pkg.go.dev/log/slog
Not sure I understand your sarcasm. I read the documentation, source code, handler writing guide, and issues in the Go repository multiple times over two years, and I use slog extensively. Go is my primary language since r60. I think I know how to read Go docs.

Now, please point me to the place in the documentation that says if I can or can't use a value implementing the error interface as an attribute value, and will the handler or something else would call the `Error() string` method.

My definition of "supported" is simple – I could pass a supported value to the logger and get a reasonable representation from any handler. In my example, the JSON handler does not provide it for the fmt.Stringer.

That seems to work as expected?

The output of data is handled by the handler. Such behaviour is clearly outlined in the documentation by the JSONHandler. I wouldn't expect a JSONHandler to use Stringer. I'd expect it to use the existing JSON interfaces, which it does.

I'd expect the Text handler to use TextMarshaller. Which it does. Or Stringer, which it does implicitly via fmt.Sprintf.

My problem with that is that it makes it impossible to use slog logger safely without knowing what handler is being used. Which kind of defeats the purpose of defining the common structured logging interface.
> Which kind of defeats the purpose of defining the common structured logging interface.

Does it, though? Why would the log producer care about how the log entires are formatted? Only the log consumer cares about that.

As a producer of the response, if I didn't care about being understood, I would use a made-up language. As a consumer, you may care about understanding my response, but you cannot do anything about it.