Let's talk about logging

March 31, 2026

I’ve been absent far too long. Most days I think about writing something again, then… I don’t. 🤷‍♂️ I’m going to try to get back into the habit… and this time, I thought I’d talk about one of my favorite packages in the standard library: log/slog.

To kick off, I’ll give a general description of the package, then starting tomorrow (I promise! I won’t forget again!) we’ll start into the particulars.

First there was log

The log package has been with us since the very beginning of Go. But few use it for serious applications, because it’s so limited. No real log levels–just “info” (or info + panic, or info + os.Exit), plain text output (no JSON or other formats), and thus no meaningful middleware capabilities.

These limitations lead to the proliferation of many third-party logging libraries such as Logrus, Zap, and ZeroLog.

Introducing log/slog

Finally, in June, 2023, with Go 1.21, log/slog was introduced. And now, except for very special cases, it should probably be your go-to logger. It offers:

  • LevelsDebug, Info, Warn, and Error by default, but it’s customizable, so you can also have Super Duper Important I really Mean it, I do! as a level, if you want!
  • Structured output — Key/value pairs for the win!
  • Pluggable handlers — Want JSON output? Handled. Standard text more to your liking? You got it. Want color on the console? No problem.
  • Composable handlers — Want JSON sent to your logging service, plain text to your log file, and color to your console, all at the same time? We have that, too!
  • Middleware — Want to filter passwords out of your logs? Or add special annotations to every error? Or replace every occurrence of the word “hippopotamus” with “rhinoceros” for unexplicable reasons? You can.

If you’ve never used log/slog, this series will be for you.

If you already use log/slog, you’ll probably learn something new anyway!

So let’s go!


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:


Attribute evaluation

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.


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.

Get daily content like this in your inbox!

Subscribe