Intro to slog levels

By default, the log/slog package supports four log levels: Debug, Info, Warn, and Error, each of which has matching logger methods: Overview … The Info top-level function calls the Logger.Info method on the default Logger. In addition to Logger.Info, there are methods for Debug, Warn and Error levels. Besides these convenience methods for common levels, there is also a Logger.Log method which takes the level as an argument. Each of these methods has a corresponding top-level function that uses the default logger.


slog Records and levels

Yesterday we saw that slog uses handlers to process/record logs. But what exactly does it process? Records! Overview … A log record consists of a time, a level, a message, and a set of key-value pairs, where the keys are strings and the values may be of any type. As an example, slog.Info("hello", "count", 3) creates a record containing the time of the call, a level of Info, the message “hello”, and a single pair with key “count” and value 3.


Anatomy of a log/slog logger

Unlike the older log package, which provides a single *log.Logger type as its primary interface, log/slog has a two-tiered architecture. This is roughly the same architecture used by the database/sql package: One interface implements a handler (or “driver” for database/sql), and another interface is consumed. The package itself provides the intermediate translation. This is essentially a localized example of ports-and-adaptors or hexagonal architecture. Here’s how the GoDoc for the package explains it:


Let's talk about logging

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.

Subscribe to Boldly Go: Daily

Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.

Unsure? Browse the archive.

Other

1 min read


GopherCon Early Gopher Tickets available

I normally try to share Go tips and tricks, mini tutorials on this list, rather than promoting things. But I’m going to make an exception today, but to promote something that itself provides tips, tricks, and mini tutorials (and so much more)! Earlier this week, GopherCon 2026 tickets went on sale, and through the end of the month, Early Gopher tickets are on sale for $200 less than the regular price.


Happy late new year

I kinda fell off the planet for a while with family, holidays, and… writers’s block. But I’m back now, and prepared to take the time to come up with (hopefully) interesting things to write about again. Today I’m going to talk about a new feature coming in Go 1.26, which relates to my series earlier in 2025 about contexts. The new feature is mentioned very briefly in the release notes:

Iterators

2 min read


Iterator callbacks

Today I want to expand with a thought I touched on yesterday: Callbacks with iterators. Yesterday’s context was error handling. But I’ve found callbacks with iterators to be very valuable in a slightly different case. To understand the problem callbacks have helped me solve, we need to move away from our grep example to something a bit more involved. Let’s imagine we’re querying a REST API, which returns paginated results. A typical response body might look something like this, in JSON:

Iterators

3 min read


Alternatives to iter.Seq3

Last time we modified our range-over-func iterator to return a iter.Seq2, so that it could include a possible error value for each iteration. But this isn’t the only way to handle errors with range-over-func. And in fact, in some cases, it may not even be possible! Suppose you’re ranging over a key/value pair, for example. There is no iter.Seq3 option to return three values per iteration. To illustrate, let’s update our grep to return the line number, and the matching line:

Iterators

3 min read


Handling errors during iteration with range-over-func

Yesterday we looked at a range-over-func iterator that was missing a vital piece: Error handling during iteration. I know of three possible solutions to this problem, and today we’ll look at the simplest of them, which I typically recommend: Using iter.Seq2. We’ve already looked at this pattern from the consumer’s perspective. Today we’ll see how the implementation works. func grep(r io.Reader, pattern string) (iter.Seq2[string, error], error) { re, err := regexp.

Iterators

2 min read


Implementing a range-over-func iterator

It’s finally time to look at how we implement a range-over-func iterator. I’d venture a guess this should be the go-to pattern for most iterators, unless or until you have special needs that it won’t address. And, of course, we’ll discuss some of those in the near future, as well. First, here’s how our grep implementation looks using range-over-func: func grep(r io.Reader, pattern string) (iter.Seq[string], error) { re, err := regexp.

Iterators

2 min read


Separating iteration from advancement

Last week we looked at a simple custom iterator pattern: type Result struct {/* ... */} func (*Result) Next() (string, bool) func (*Result) Err() error But let’s talk about a few variations, and when they might make sense. First, I already mentioned last week that in some cases we could simply eliminate the bool value if the zero value of the iterated value can serve as an indication that iteration has completed.