Last active
December 18, 2024 15:14
-
-
Save Integralist/09ff73d309af7852864dfe709c53f12d to your computer and use it in GitHub Desktop.
[Structured Logging in Go] #go #golang #slog #log #structuredlogging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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