RSSAmplifier

Blog

Boldly Go

Recent content on Boldly Go

boldlygo.techRSS feed ↗590 posts

Latest posts

slog Handler methods

slog.Handler is an interface type. We’re going to look at each method on that interface in turn. Understanding these methods is valuable if you’re ever implementing your own handler. If you’re only using slog to produce logs using existing handlers, you could skip this part… though maybe you’ll learn something interesting anyway! type Handler type Handler interface {…

Attribute equality

func (Attr) Equal func (a Attr) Equal(b Attr) bool Equal reports whether a and b have equal keys and values. That’s a pretty obvious, and opaque statement. How is equality determined? We have to look at the source to determine: func (a Attr) Equal(b Attr) bool { return a.Key == b.Key && a.Value.Equal(b.Value) } Okay, so it returns true if the keys are equal (that’s simple—they’re…

slog.GroupAttrs

Last time we looked at slog.Group, which accepts the variadic ...any as arguments. But there’s also a version that accepts slog.Attr values: func GroupAttrs func GroupAttrs(key string, attrs ...Attr) Attr GroupAttrs returns an Attr for a Group Value consisting of the given Attrs. GroupAttrs is a more efficient version of Group that accepts only Attr values. But notice there’s no mixed…

slog.Group

The one Attr constructor not yet covered: func Group func Group(key string, args ...any) Attr Group returns an Attr for a Group Value. The first argument is the key; the remaining arguments are converted to Attrs as in Logger.Log. Use Group to collect several key-value pairs under a single key on a log line, or as the result of LogValue in order to log a single value as multiple Attrs.

The Attr type

Other than logger methods, the slog.Attr type is probably the thing you’ll use the most in your use of the slog package: Types type Attr type Attr struct { Key string Value Value } An Attr is a key-value pair. This type has several type-specific constructors, too, but we’re going to do an abbrevaited look at them, because they’re highly repetitive. func Any func Any(key string,…

Backward compatibility

We’ve already discussed when the default log/slog Logger also serves as the default log Logger. But what if you want to send log logs to a log/slog Logger, without using the global defaults? Introducing NewLogLogger! func NewLogLogger func NewLogLogger(h Handler, level Level) *log.Logger NewLogLogger returns a new log.Logger such that each call to its Output method dispatches a Record to the…

The global logger

I’m going to do something I haven’t done before in these stdlib tours, and that is skip over a huge section of the GoDoc. That’s becase a huge section here is entirely redundant with what comes later: func Debug func Debug(msg string, args ...any) Debug calls Logger.Debug on the default logger. And we have virtually identical entries for each of the following: DebugContext Error…

slog Constants

We’ve made it through the overview of the slog documentation. Now it’s time to get down and dirty! First up, constants! There are four, and they all define built-in attribute keys: Constants const ( // TimeKey is the key used by the built-in handlers for the time // when the log method is called. The associated Value is a [time.Time]. TimeKey = 'time' // LevelKey is the key used by the…

Concurrent logging

One last note in the performance section… How does logging work in a concurrent system? Performance considerations … The built-in handlers acquire a lock before calling io.Writer.Write to ensure that exactly one Record is written at a time in its entirety. Although each log record has a timestamp, the built-in handlers do not use that time to sort the written records. User-defined handlers…

Back after an unannounced absence

Hey everyone… I dropped the ball! A combination of unexpected family events, prepping for a conference, and some travel, meant I haven’t been writing for much longer than I like. But I’m back! So where were we? Oh that’s right… performance considerations with log/slog. We had looked at using the fmt.Stringer interface to avoid eager processing with slog.TextHandler.…

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…

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…

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…

Working with Records

In my experience, it’s rare you’ll need to worry about slog Records, unless you’re writing a Handler, or some kind of middleware/transform. But even if you never need to manipulate a Record directly, understanding the concept can be useful. Working with Records Sometimes a Handler will need to modify a Record before passing it on to another Handler or backend. A Record contains a…

Wrapping output methods

Let’s talk about a feature I’ve never used, or even knew existed… I mentioned a while back that the AddSource field of HandlerOptions controls whether the log output includes the source code position of the log call. But what if that log call is wrapped by a helper, obscuring the meaningful source position? Wrapping output methods The logger functions use reflection over the…

Customizing a type's logging behavior

You may find cases where you wish to control how a value is logged, differently than how it’s used in other contexts. The log/slog package gives you a lot of flexibility in this regard, for custom types: Customizing a type’s logging behavior If a type implements the LogValuer interface, the Value returned from its LogValue method is used for logging. You can use this to control how…

Values

TIL Logger.LogAttrs is a thing! But what is that thing?? Yesterday I mentioned that using an slog.Attr can be marginally more efficient than using naked key/value pairs in a log call. While true, that glosses over what is likely to be a much more impactful performance consideration in certain applications… Attrs and Values … The value part of an Attr is a type called Value. Like an [any], a…

Attrs

We’ve been talking about key/value pairs. The log/slog package has a name for these: Attr (short for “attribute”). And there’s more than one way to build an attribute: Attrs and Values An Attr is a key-value pair. The Logger output methods accept Attrs as well as alternating keys and values. The statement slog.Info('hello', slog.Int('count', 3)) behaves the same as…

Contexts

[**Idiomatic Testing in Go**](/idiomatic-testing/) starts TOMORROW! My my, how time flies! There are still a few seats available, and there’s still time to sign up. Learn how to get the most out of the tests in your Go app! One feature I often see overlooked capability of the log/slog package, is to extract log key/value pairs from context: Contexts Some handlers may wish to include…

More with Groups

Organizing log key/value pairs by group is a nice way to organize your logging data, but what about organizing the way your application groups data? Maybe you want all logs created by a particular code path to be grouped together. How can you accomplish this? Enter WithGroup… Groups … Use Logger.WithGroup to qualify all of a Logger’s output with a group name. Calling WithGroup on a…

Groups

Early bird registration for [**Idiomatic Testing in Go**](/idiomatic-testing/) ends today! Not sure how to adapt your xUnit habits to Go? This is the course for you! Sign up today to save 25% over the full price. – Sometimes you want to group several key/value attributes together when logging. Maybe different aspects of an error (error_code, error_detail, stacktrace, etc), or different…

Filtering logs by level

Levels In an application, you may wish to log messages only at a certain level or greater. One common configuration is to log messages at Info or higher levels, suppressing debug logging until it is needed. The built-in handlers can be configured with the minimum level to output by setting [HandlerOptions.Level]. The program’s main function typically does this. The default value is…

Pop quiz: pass or fail?

I'm launching a new live course: **Idiomatic Testing in Go**. The course begins May 5. Early-bird pricing is in effect until April 28. Why Go avoids assert libraries Better alternatives to mocks Make writing tests fun! Today’s post is a taste of what we’ll cover. See pricing & reserve → Pop quiz. Does this testify assertion pass or fail? var x []int y := []int{} require.Equal(t, x, y)…

Testify is making your Go tests worse

Pop quiz. Does this testify assertion pass or fail? var x []int y := []int{} require.Equal(t, x, y) If you’re like me, you have no idea. Arguments for both passing and failing seem reasonable. Let’s jump to the docs: func Equal func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool Equal asserts that two objects are equal. Well that’s no help. I…

Log levels

I'm launching a new live course: **Idiomatic Testing in Go**. The course begins May 5. Early-bird pricing is in effect until April 28. Why Go avoids assert libraries Better alternatives to mocks Make writing tests fun! See pricing & reserve → log/slog provides some rather sophisticated capabilities around log levels. We’ll get into it, but the good news is, you don’t need to care about…

Logging common fields

It’s common that you’ll want to include certain attributes in all logs in an application or component. log/slog makes this pretty easy. Overview … Some attributes are common to many log calls. For example, you may wish to include the URL or trace identifier of a server request with all log events arising from the request. Rather than repeat the attribute with every log call, you can…

Handler configuration

The default slog handlers are quite configurable. Overview … Both TextHandler and JSONHandler can be configured with HandlerOptions. There are options for setting the minimum level (see Levels, below), displaying the source file and line of the log call, and modifying attributes before they are logged. While HandlerOptions only exposes three fields: AddSource bool Level Leveler ReplaceAttr…

Chosing an slog handler

log/slog ships with two default handlers: the TextHandler and the JSONHandler. Overview … For more control over the output format, create a logger with a different handler. This statement uses New to create a new logger with a TextHandler that writes structured records in text form to standard error: logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) TextHandler output is a sequence of…

Overriding the default handler

Today I’m going to jump around a bit in the GoDoc, to talk about a topic I mentioned last time: how to override the default logger. Overview … Setting a logger as the default with slog.SetDefault(logger) will cause the top-level functions like Info to use it. SetDefault also updates the default logger used by the log package, so that existing applications that use log.Printf and related…

The default handler

Much like the older log package, log/slog ships with a “default logger”. You can use this logger without doing any configuration, just by calling any of the package-level logging functions: slog.Error('oh noes!') While you’d probably never want to use the default logger in a serious server application, it can be a convenience for small or throw-away utilities. But how does it…

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…

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…

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…

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…

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…

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…

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…

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…

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

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…

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…

Building a custom iterator

We’ve looked at using channels as iterators, and found they’re hardly ideal. Let’s look at the next obvious answer: custom iterators. Before range-over-func, which we’ll get to next, custom iterators were really the only meaningful solution. And they still remain a very viable one, because of their great flexibility. Let’s start with a simple implementation of a…

Closing a channel iterator early

I’ve been away for a while. Last week I spoke at GoWest 2025, and have been just generally busy. But now I’m ready to pick up on the topic I started nearly two weeks ago: Drawbacks of channel-based iterators! In addition to the issue of error handling with a channel-based iterator, there’s the potentially stickier issue of how to abort iterating early. To illustrate, let’s…

Implementing a channel-based iterator

Today let&rsquo;s look at re-implementing our non-iterating grep function using a channel for iteration. First the code: func grep(r io.Reader, pattern string) (chan <- string, error) { re, err := regexp.Compile(pattern) if err != nil { return nil, err } matches := make(chan string) go func() { defer close(matches) scanner := bufio.NewScanner(r) var matches []string for scanner.Scan() { line :=…

Implementing iterators

The last few weeks I&rsquo;ve been talking about different iterator patterns, but from the perspective of consuming iterators. Let&rsquo;s switch angles now, and begin talking about how to implement iterators. To illustrate, let&rsquo;s use a simple example of a grep utility. It will read an io.Reader, and return any lines that match a regular expression input. Here&rsquo;s a simple…

Errors with range over func

Last week I introduced the topic of range-over-func as an iterator pattern. Let&rsquo;s look at how error handling differs with this approach. For context, let&rsquo;s compare the custom iterator approach, which has three distinct errors to check: 1. The initial function call, 2. while iterating over each row, 3. finally at the end, to ensure iteration completed successfully. orders, err :=…

Range over func

Since Go 1.23, we&rsquo;ve had a new way we can implement iterators. I&rsquo;ve written previously about it if you&rsquo;re interested. But today we&rsquo;ll take a glance at how it affects our Orders example: orders, err := db.Orders(ctx, userID) if err != nil { return err } for order, err := range orders { if err != nil { return err } /* Do something with each order */ } Unless you&rsquo;re…

Custom iterators

Until recently, custom iterators were probably the most common way to iterate over a list of elements that might trigger an error. Several examples exist in the standard library. Perhaps the most well known would be the sql.Rows type, which provides (among others), the following methods: Next() bool — Advances to the next item Scan(...any) error — Processes the current item Err() error — Reports…

Iterating over channels

For this discussion of iterators, let&rsquo;s establish a baseline example. It&rsquo;s made up, but realistic and common: A database method that returns all user orders. We&rsquo;ll be experimenting with different function signatures, but in general, this is what we can imagine it will look like: func (DB) Orders(ctx context.Context, userID string) ([]*Order, error) And we would consume it with…

Iterator patterns

I&rsquo;m going to change gears from the previous discussion about goroutines, to different ways of iterating. This can closely relate to goroutines, so we&rsquo;ll bounce back and forth a bit, I suspect. Meanwhile, if you have any questions specifically about goroutines that I didn&rsquo;t cover, send me an email. I&rsquo;ll be happy to fill in those gaps! First off, before really diving into…