Last time I described slog.DiscardHandler as a viable way to silence logs in tests.
Now I want to tell you why I never use that option, and you probably shouldn’t, either.
But first a story.
The slog package was introduced with Go 1.21 in August of 2023. It didn’t yet have slog.DiscardHandler, so you had to roll your own. But that was easy:
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
Easy as it was, it was also annoying. So in Go 1.24, slog.DiscardHandler was added, making it slightly easier.
logger := slog.New(slog.DiscardHandler)
But then in Go 1.25, something even better was introduced, and it’s what I always use now. But it came from a different place: testing.T.Output
What does T.Output do, and how does it relate to logging?
func (*T) Output
func (c *T) Output() io.WriterOutput returns a Writer that writes to the same test output stream as TB.Log. The output is indented like TB.Log lines, but Output does not add source locations or newlines. The output is internally line buffered, and a call to TB.Log or the end of the test will implicitly flush the buffer, followed by a newline. After a test function and all its parents return, neither Output nor the Write method may be called.
(Note the same method exists on testing.B, and testing.F as well).
In most cases, logging to t.Output() is always better than logging to the discard handler. In the usual case, the observable output is identical: Nothing.
But, in the case your test fails, the log output is now included in your test output. And that can be very valuable for debugging or diagnosing a test failure.
logger := slog.New(slog.NewTextHandler(t.Output(), nil))
What’s more, this trick isn’t limited to slog. You can use the exact same t.Output() writer for the log package, any third-party logging package, or any other writer that has nothing to do with logs at all!