Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active December 18, 2024 15:14
Show Gist options
  • Save Integralist/09ff73d309af7852864dfe709c53f12d to your computer and use it in GitHub Desktop.
Save Integralist/09ff73d309af7852864dfe709c53f12d to your computer and use it in GitHub Desktop.
[Structured Logging in Go] #go #golang #slog #log #structuredlogging
// 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")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment