« Back to Index

Go: Structured Logging

View original Gist on GitHub

Tags: #go #logs

main.go

// https://play.golang.com/p/tfop7CgoGF5
// https://goplay.tools/snippet/aZatTtNaElZ

package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
)

type foo struct {
	beep string
	boop int
	blah bool
}

func (f foo) LogValue() slog.Value {
	return slog.GroupValue(
		slog.String("beep", f.beep),
		slog.Int("boop", f.boop),
		slog.Bool("blah", f.blah),
	)
}

// IMPORTANT: [(slog.HandlerOptions).ReplaceAttr] docs say not to mutate `groups` slice.
// This means, you can't solve the problem of multiple groups with the same name.
// Ultimately, you will end up with invalid JSON (two keys with the same name == invalid).
// So it's your responsibility to avoid that situation.
// Which means storing your attributes in a slice and waiting for a single log event to add a group.
// You can't add a group and then later append to it (as demonstrated below).

func main() {
	ctx := context.Background()
	attrs := []any{slog.String("foo", "a"), slog.String("bar", "b")}
	
	// The replacer function is resolved once per logger instance.
	// i.e. it is not called on each log event trigger (e.g. LogAttrs).
	replacer := func(groups []string, a slog.Attr) slog.Attr {
		if len(groups) > 0 {
			if groups[0] == "a_struct" {
				return slog.Attr{} // delete the attribute from the group (will run for every attribute in the group, and ultimately will result in the group itself being removed)
			}
			fmt.Printf("groups: %#v\n", groups)
			fmt.Printf("attribute: %#v\n", a)
		}
		return a
	}
	handler := &slog.HandlerOptions{Level: slog.LevelInfo, ReplaceAttr: replacer}
	logger := slog.New(slog.NewJSONHandler(os.Stdout, handler))
	
	// Create a new logger instance with `a_struct` group.
	logger = logger.With(slog.Any("a_struct", foo{"beeping", 123, true}))

	// Append new attribute to our `attrs` slice.
	attrs = append(attrs, slog.String("baz", "c"))
	
	// Create a new logger instance with `a_group` group using our `attrs` slice.
	logger = logger.With(slog.Group("a_group", attrs...))
	
	// Although we append a new attribute to the `attrs` slice,
	// the attribute will not show up in the following log event,
	// as it was appended AFTER the logger instance was created.
	attrs = append(attrs, slog.String("qux", "d"))
	
	logger.LogAttrs(ctx, slog.LevelInfo, "some_event_1")
	
	// If I try to create a new logger instance, 
	// I'll end up with invalid JSON (i.e. double "a_group" fields)
	logger = logger.With(slog.Group("a_group", attrs...))
	
	logger.LogAttrs(ctx, slog.LevelInfo, "some_event_2")
}