Groups

April 28, 2026

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 aspects of an HTTP response (bytes_sent, status_code, etc). The log/slog package gives us this!

Groups

Attributes can be collected into groups. A group has a name that is used to qualify the names of its attributes. How this qualification is displayed depends on the handler. TextHandler separates the group and attribute names with a dot. JSONHandler treats each group as a separate JSON object, with the group name as the key.

Use Group to create a Group attribute from a name and a list of key-value pairs:

slog.Group("request",
    "method", r.Method,
    "url", r.URL)

TextHandler would display this group as

request.method=GET request.url=http://example.com

JSONHandler would display it as

"request":{"method":"GET","url":"http://example.com"}

Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Expanding errors with ReplaceAttr

Let’s look at one other example where I reach for ReplaceAttr regularly: Logging additional error detail. (I talk more about this approach in my Boot.Dev course Learn Logging and Observability in Go (see below for a discount code).) For this technique to be meaningful, I first need errors that contain additional details. And this can take many forms, but probably the most ubiquitous form is an error that contains a stack trace, such as those produced by the popular package github.


Test logging with ReplaceAttr

Today’s topic is probably the most interesting, subtle, and complicated of the three HandlerOptions fields: ReplaceAttr. type HandlerOptions type HandlerOptions struct { … // ReplaceAttr is called to rewrite each non-group attribute before it is logged. // The attribute's value has been resolved (see [Value.Resolve]). // If ReplaceAttr returns a zero Attr, the attribute is discarded. // // The built-in attributes with keys "time", "level", "source", and "msg" // are passed to this function, except that time is omitted // if zero, and source is omitted if AddSource is false.


Handler level

Each handler has an optional Level value, which controls which logs to drop, and which to actually log: type HandlerOptions type HandlerOptions struct { … // Level reports the minimum record level that will be logged. // The handler discards records with lower levels. // If Level is nil, the handler assumes LevelInfo. // The handler calls Level.Level for each record processed; // to adjust the minimum level dynamically, use a LevelVar.

Get daily content like this in your inbox!

Subscribe