Live data from Hacker News

Logging in Go with Slog: A Practitioner's Guide

dash0.com

21–30 of 59 posts

Re: Logging in Go with Slog: A Practitioner's Guide

#21
post #16

Earlier quoted context omitted.

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.

Re: Logging in Go with Slog: A Practitioner's Guide

#22
post #12

Earlier quoted context omitted.

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"…

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.

Re: Logging in Go with Slog: A Practitioner's Guide

#23
It's fine for application logging but I have two gripes with slog:

1) If you're writing a library that can be used by many different applications and want to emit logs, you'll still need to write a generic log interface with adapters for slog, zap, charmlog, etc. That the golang team refuses to bless a single interface for everyone to settle on both makes sense given their ideological standpoint on shipping interfaces and also causes endless mild annoyance and code duplication.

2) I believe it's still impossible to see the correct callsite in test logs when using slog as the logger. For more information, see https://github.com/neilotoole/slogt?tab=readme-ov-file#defic.... It's possible I'm out of date here — please correct me if this is wrong, it's actually a much larger annoyance for me and one of the reasons I still use uber/zap or charmbracelet/log.

Overall, especially given that it performs worse than uber/zap and everyone has basically standardized on that and it provides essentially the same interface, I recommend using uber/zap instead.

EDIT: just to expand further, take a look at the recommended method of wrapping helper methods that call logs. Compare to the `t.Helper()` approach. And some previous discussion. Frustrating!

- https://pkg.go.dev/log/slog#example-package-Wrapping

- https://github.com/golang/go/issues/59145#issuecomment-14770...

Re: Logging in Go with Slog: A Practitioner's Guide

#24

It's fine for application logging but I have two gripes with slog: 1) If you're writing a library that can be used by many different applications and want to emit logs, you'll still need to write a generic log interface with adapters for slog, zap, charmlog, etc. That the golang team refuses to bless a single interface for everyone to settle on both makes sense given their ideological standpoint on shipping interface…

1) The idea is that your library should accept the slog logger and use it. The caller would create a logger with a handler that defines how log messages are handled. But there are problems with supported types; see my other comments.

2) It is improved in 1.25. See https://github.com/golang/go/issues/59928 and https://pkg.go.dev/testing#T.Output. Now it is possible to update slogt to provide correct callsite – the stack depth should be the same.

Re: Logging in Go with Slog: A Practitioner's Guide

#25
> The key decision is thus between two patterns: using a global logger or using dependency injection. The former is extremely convenient but adds a hidden dependency that’s hard to test, while the latter is more verbose but makes dependencies explicit, resulting in highly testable and flexible code.

Curious how different people handle this. I personally pretty much always pass a logger into function, classes, structs (what have you) so it has the context I need it to. It's a tad more verbose I guess, but it's such a minor lift I've always found it worth it.

Re: Logging in Go with Slog: A Practitioner's Guide

#26

It's fine for application logging but I have two gripes with slog: 1) If you're writing a library that can be used by many different applications and want to emit logs, you'll still need to write a generic log interface with adapters for slog, zap, charmlog, etc. That the golang team refuses to bless a single interface for everyone to settle on both makes sense given their ideological standpoint on shipping interface…

We switched to zerolog a while back and didn't look back.

Re: Logging in Go with Slog: A Practitioner's Guide

#27
post #25

> The key decision is thus between two patterns: using a global logger or using dependency injection. The former is extremely convenient but adds a hidden dependency that’s hard to test, while the latter is more verbose but makes dependencies explicit, resulting in highly testable and flexible code. Curious how different people handle this. I personally pretty much always pass a logger into function, classes, structs…

> more verbose

I'd say necessarily verbose. Without injection, it is not immediately apparent that something is dependent on something else (in this case a logger) with side effects, which ultimately harms understandability.

I expect by "more verbose" the author really meant "in need of more typing". I am not sure optimizing for less typing ever a good tradeoff. And if you can find good reason to optimize for less typing, you're not going to be choosing a structured language in the first place, so...

Re: Logging in Go with Slog: A Practitioner's Guide

#28
post #25

> The key decision is thus between two patterns: using a global logger or using dependency injection. The former is extremely convenient but adds a hidden dependency that’s hard to test, while the latter is more verbose but makes dependencies explicit, resulting in highly testable and flexible code. Curious how different people handle this. I personally pretty much always pass a logger into function, classes, structs…

In any API service, it's better to handle via dependency injection IMO.

Instantiate all of your metadata once, and then send that logger down, so that anybody who uses that logger is guaranteed to have the right metadata... the time to add logging is not when you are debugging.

Re: Logging in Go with Slog: A Practitioner's Guide

#29
post #27
post #25

> The key decision is thus between two patterns: using a global logger or using dependency injection. The former is extremely convenient but adds a hidden dependency that’s hard to test, while the latter is more verbose but makes dependencies explicit, resulting in highly testable and flexible code. Curious how different people handle this. I personally pretty much always pass a logger into function, classes, structs…

> more verbose I'd say necessarily verbose. Without injection, it is not immediately apparent that something is dependent on something else (in this case a logger) with side effects, which ultimately harms understandability. I expect by "more verbose" the author really meant "in need of more typing". I am not sure optimizing for less typing ever a good tradeoff. And if you can find good reason to optimize for less ty…

Totally agree. I'll suffer with an additional 20 characters of typing. I hadn't even considered the other ways the article goes into, I'd always used a child logger and passed it down, so I'm a bit reassured that I haven't been a _complete_ fool for all these years.

Re: Logging in Go with Slog: A Practitioner's Guide

#30

It's fine for application logging but I have two gripes with slog: 1) If you're writing a library that can be used by many different applications and want to emit logs, you'll still need to write a generic log interface with adapters for slog, zap, charmlog, etc. That the golang team refuses to bless a single interface for everyone to settle on both makes sense given their ideological standpoint on shipping interface…

> That the golang team refuses to bless a single interface for everyone to settle on

Uh... https://pkg.go.dev/golang.org/x/exp/slog#Handler

If zap, charmlog, etc. don't provide conformance to the interface, that's not really on the Go team. It wouldn't be that hard to write your own adapter around your unidiomatic logger of choice if you're really stuck, though. This isn't an actual problem unless you think someone else owes you free labor for some reason.

Post reply on HN