More with Groups

April 29, 2026

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 Logger results in a new Logger with the same Handler as the original, but with all its attributes qualified by the group name.

This can help prevent duplicate attribute keys in large systems, where subsystems might use the same keys. Pass each subsystem a different Logger with its own group name so that potential duplicates are qualified:

logger := slog.Default().With("id", systemID)
parserLogger := logger.WithGroup("parser")
parseInput(input, parserLogger)

When parseInput logs with parserLogger, its keys will be qualified with “parser”, so even if it uses the common key “id”, the log line will have distinct keys.

So we’ve already seen how you can use With to add attributes to all logs in a logger:

logger = logger.With("request_id", req.Get("ID"))

WithGroup works the same way, but any additional keys added to the returned logger are added to the group:

func FooMiddleware(logger *slog.Logger) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        logger = logger.With("foo_middleware")

        /* ... */
        logger.Info("Handling request", "method", r.Method)

        /* ... */
        logger.Debug("Checking credentials")

        /* ... */
        if err != nil {
            logger.Error("foo failed", "error", err")
        }
    }
}

In this above example, all three logs add their keys inside the “foo_middleware” group, without having to specify the group more than once.


Share this

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

Unsure? Browse the archive .

Related Content


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 are responsible for their own locking and sorting.


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. But let’s now look at a more general solution: Performance considerations …


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:

Get daily content like this in your inbox!

Subscribe