diff --git a/README.md b/README.md index da162fd..96d0ae3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Mirror: https://github.com/wisehodl/go-mana-component - Injects a named component identity into a `context.Context` at library boundaries - Propagates module identity and component hierarchy across layers -- Provides `slog` attributes for structured logging and a string map for generic consumers +- Implements `slog.LogValuer` for use as a structured log attribute, and provides a string map for generic consumers ## What this library does not do @@ -40,15 +40,15 @@ constructs a logger internally. ### At a library boundary A top-level constructor receives a context and creates a new component -identity. Injecting the component attributes on the logger allows it to carry -`module` and `path` automatically. +identity. Passing the component as a `slog.Any` group attribute allows the +logger to carry `module` and `path` automatically. ```go func NewPool(ctx context.Context, id string, handler slog.Handler) (*Pool, error) { ctx = component.MustNew(ctx, "honeybee", "outbound_pool") - attrs, _ := component.Attrs(ctx) - logger := slog.New(handler).WithAttrs(attrs).With(slog.String("pool_id", id)) + c, _ := component.Get(ctx) + logger := slog.New(handler).With(slog.Any("component", c), slog.String("pool_id", id)) return &Pool{ctx: ctx, logger: logger}, nil } @@ -63,8 +63,8 @@ path. No parent identifiers need to be passed as arguments. func NewWorker(ctx context.Context, id string, handler slog.Handler) (*Worker, error) { ctx = component.MustExtend(ctx, "outbound_worker") - attrs, _ := component.Attrs(ctx) - logger := slog.New(handler).WithAttrs(attrs).With(slog.Any("peer_id", id)) + c, _ := component.Get(ctx) + logger := slog.New(handler).With(slog.Any("component", c), slog.Any("peer_id", id)) return &Worker{ctx: ctx, logger: logger}, nil } diff --git a/main.go b/main.go index 66c4cc2..dbc7522 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ type Component interface { Module() string Path() []string PathString() string + LogValue() slog.Value } type component struct { @@ -27,6 +28,12 @@ type component struct { func (c component) Module() string { return c.module } func (c component) Path() []string { return slices.Clone(c.path) } func (c component) PathString() string { return strings.Join(c.path, ".") } +func (c component) LogValue() slog.Value { + return slog.GroupValue( + slog.String("module", c.module), + slog.String("path", c.PathString()), + ) +} func insert(ctx context.Context, module string, name string, path []string) context.Context { return context.WithValue(ctx, storageKey, component{ @@ -117,16 +124,3 @@ func GetFields(ctx context.Context) (map[string]string, bool) { "path": c.PathString(), }, true } - -// Attrs returns the slog projection of GetFields. -func Attrs(ctx context.Context) ([]slog.Attr, bool) { - fields, ok := GetFields(ctx) - if !ok { - return nil, false - } - - return []slog.Attr{ - slog.String("module", fields["module"]), - slog.String("path", fields["path"]), - }, true -} diff --git a/main_test.go b/main_test.go index 01da8c8..e1f7556 100644 --- a/main_test.go +++ b/main_test.go @@ -2,7 +2,6 @@ package component import ( "context" - "log/slog" "slices" "testing" ) @@ -120,22 +119,18 @@ func TestGetFields(t *testing.T) { } } -func assertAttr(t *testing.T, attr slog.Attr, key string, value string) { - t.Helper() - if attr.Key != key { - t.Errorf("expected attr key %q, got %q", key, attr.Key) - } - if attr.Value.String() != value { - t.Errorf("expected attr value %q, got %q", value, attr.Value.String()) - } -} - -func TestAttrs(t *testing.T) { +func TestLogValue(t *testing.T) { ctx := MustNew(context.Background(), "mymodule", "mycomponent") - attrs, ok := Attrs(ctx) - if !ok { - t.Fatal("expected attrs in context") + c, _ := Get(ctx) + v := c.LogValue() + attrs := v.Group() + if len(attrs) != 2 { + t.Fatalf("expected 2 attrs, got %d", len(attrs)) + } + if attrs[0].Key != "module" || attrs[0].Value.String() != "mymodule" { + t.Errorf("expected module=mymodule, got %s=%s", attrs[0].Key, attrs[0].Value.String()) + } + if attrs[1].Key != "path" || attrs[1].Value.String() != "mycomponent" { + t.Errorf("expected path=mycomponent, got %s=%s", attrs[1].Key, attrs[1].Value.String()) } - assertAttr(t, attrs[0], "module", "mymodule") - assertAttr(t, attrs[1], "path", "mycomponent") }