From 65d0f0122610de5176249642fac74f0bfe2ce0d1 Mon Sep 17 00:00:00 2001 From: Nick <73077675+tmzane@users.noreply.github.com> Date: Tue, 12 May 2026 11:54:57 +0300 Subject: [PATCH 1/2] feat: rename module, add slogattr --- README.md | 14 ++-- go.mod | 2 +- slogattr/attr.go | 61 ++++++++++++++++++ slogattr/attr_test.go | 74 ++++++++++++++++++++++ context.go => slogctx/context.go | 1 + handler.go => slogctx/handler.go | 0 handler_test.go => slogctx/handler_test.go | 12 ++-- 7 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 slogattr/attr.go create mode 100644 slogattr/attr_test.go rename context.go => slogctx/context.go (87%) rename handler.go => slogctx/handler.go (100%) rename handler_test.go => slogctx/handler_test.go (88%) diff --git a/README.md b/README.md index 6f4dc50..a2ec239 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,13 @@ -# slogctx +# slogutil -[![checks](https://github.com/go-simpler/slogctx/actions/workflows/checks.yaml/badge.svg)](https://github.com/go-simpler/slogctx/actions/workflows/checks.yaml) -[![docs](https://pkg.go.dev/badge/go-simpler.org/slogctx.svg)](https://pkg.go.dev/go-simpler.org/slogctx) -[![codecov](https://codecov.io/gh/go-simpler/slogctx/branch/main/graph/badge.svg)](https://codecov.io/gh/go-simpler/slogctx) +[![checks](https://github.com/go-simpler/slogutil/actions/workflows/checks.yaml/badge.svg)](https://github.com/go-simpler/slogutil/actions/workflows/checks.yaml) +[![docs](https://pkg.go.dev/badge/go-simpler.org/slogutil.svg)](https://pkg.go.dev/go-simpler.org/slogutil) +[![codecov](https://codecov.io/gh/go-simpler/slogutil/branch/main/graph/badge.svg)](https://codecov.io/gh/go-simpler/slogutil) ## Install Go 1.25+ ```shell -go get go-simpler.org/slogctx +go get go-simpler.org/slogutil ``` - -## Usage - -See [tests](handler_test.go). diff --git a/go.mod b/go.mod index 91f2153..55bec45 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module go-simpler.org/slogctx +module go-simpler.org/slogutil go 1.25.0 diff --git a/slogattr/attr.go b/slogattr/attr.go new file mode 100644 index 0000000..35d1f97 --- /dev/null +++ b/slogattr/attr.go @@ -0,0 +1,61 @@ +// Package slogattr implements utilities for creating [slog.Attr]s. +package slogattr + +import ( + "cmp" + "fmt" + "log/slog" + "strconv" +) + +// ErrorKey is the key used by the [Error] function. +// The associated value is an [error]. +const ErrorKey = "error" + +// Error returns a [slog.Attr] for an [error] value. +func Error(err error) slog.Attr { + return slog.Any(ErrorKey, err) +} + +// Slice returns a [slog.Attr] for a slice of [cmp.Ordered] values. +func Slice[T cmp.Ordered](key string, ts []T) slog.Attr { + return slog.Any(key, ts) +} + +// Stringer returns a [slog.Attr] for an [fmt.Stringer] value. +func Stringer(key string, s fmt.Stringer) slog.Attr { + return slog.Any(key, s) +} + +// LogValuer returns a [slog.Attr] for a [slog.LogValuer] value. +func LogValuer(key string, v slog.LogValuer) slog.Attr { + return slog.Any(key, v) +} + +// LogValuers returns a [slog.Attr] for a slice of [slog.LogValuer] values. +// Given to [slog.JSONHandler], it will be written as {"key":{"1":...,"2":...}}, +// where ... is the [slog.LogValuer] itself. +func LogValuers[T slog.LogValuer](key string, ts []T) slog.Attr { + // A [slog.LogValuer] in a slice is not supported by [log/slog]. + // As a workaround, we convert the slice into a [slog.GroupValue]. + // More: + // - https://github.com/golang/go/issues/63204 + // - https://github.com/golang/go/issues/71088 + attrs := make([]slog.Attr, len(ts)) + for i := range ts { + attrs[i] = slog.Any(strconv.Itoa(i+1), ts[i]) + } + return slog.Attr{Key: key, Value: slog.GroupValue(attrs...)} +} + +// Lazy returns a [slog.Attr] for a lazily evaluated [cmp.Ordered] value. +func Lazy[T cmp.Ordered](key string, fn func() T) slog.Attr { + return slog.Any(key, lazy[T](fn)) +} + +type lazy[T cmp.Ordered] func() T + +// LogValue implements [slog.LogValuer]. +func (l lazy[T]) LogValue() slog.Value { + return slog.AnyValue(l()) +} diff --git a/slogattr/attr_test.go b/slogattr/attr_test.go new file mode 100644 index 0000000..505970d --- /dev/null +++ b/slogattr/attr_test.go @@ -0,0 +1,74 @@ +package slogattr_test + +import ( + "bytes" + "encoding/json" + "errors" + "log/slog" + "testing" + + "go-simpler.org/slogutil/slogattr" +) + +func TestAll(t *testing.T) { + replaceAttr := func(_ []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey || a.Key == slog.LevelKey || a.Key == slog.MessageKey { + return slog.Attr{} + } + return a + } + + var buf bytes.Buffer + h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{ReplaceAttr: replaceAttr}) + l := slog.New(h) + + l.Info("", + slogattr.Error(errors.New("oops")), + slogattr.Slice("slice", []int{1, 2, 3}), + slogattr.Stringer("stringer", slog.LevelInfo), + slogattr.LogValuer("log_valuer", point{1, 2}), + slogattr.LogValuers("log_valuers", []point{{1, 2}, {3, 4}}), + slogattr.Lazy("lazy", func() string { return "foo" + "bar" }), + ) + + var got bytes.Buffer + json.Indent(&got, buf.Bytes(), "", " ") + + const want = `{ + "error": "oops", + "slice": [ + 1, + 2, + 3 + ], + "stringer": "INFO", + "log_valuer": { + "x": 1, + "y": 2 + }, + "log_valuers": { + "1": { + "x": 1, + "y": 2 + }, + "2": { + "x": 3, + "y": 4 + } + }, + "lazy": "foobar" +} +` + if got.String() != want { + t.Errorf("\ngot: %s\nwant: %s", &got, want) + } +} + +type point struct{ x, y int } + +func (p point) LogValue() slog.Value { + return slog.GroupValue( + slog.Int("x", p.x), + slog.Int("y", p.y), + ) +} diff --git a/context.go b/slogctx/context.go similarity index 87% rename from context.go rename to slogctx/context.go index 26a0671..c5e0cce 100644 --- a/context.go +++ b/slogctx/context.go @@ -1,3 +1,4 @@ +// Package slogctx implements utilities for logging request-scoped data via [context.Context]. package slogctx import ( diff --git a/handler.go b/slogctx/handler.go similarity index 100% rename from handler.go rename to slogctx/handler.go diff --git a/handler_test.go b/slogctx/handler_test.go similarity index 88% rename from handler_test.go rename to slogctx/handler_test.go index daf06f4..5f98175 100644 --- a/handler_test.go +++ b/slogctx/handler_test.go @@ -6,12 +6,12 @@ import ( "log/slog" "testing" - "go-simpler.org/slogctx" + "go-simpler.org/slogutil/slogctx" ) func TestHandler(t *testing.T) { replaceAttr := func(_ []string, a slog.Attr) slog.Attr { - if a.Key == slog.TimeKey { + if a.Key == slog.TimeKey || a.Key == slog.LevelKey { return slog.Attr{} } return a @@ -29,9 +29,9 @@ func TestHandler(t *testing.T) { got := "\n" + buf.String() want := ` -level=INFO msg="adding foo" -level=INFO msg="adding bar" foo=1 -level=INFO msg="got foo bar" foo=1 bar=2 +msg="adding foo" +msg="adding bar" foo=1 +msg="got foo bar" foo=1 bar=2 ` if got != want { t.Errorf("\ngot: %s\nwant: %s", got, want) @@ -51,7 +51,7 @@ func bar(ctx context.Context, l *slog.Logger) { // goos: darwin // goarch: arm64 -// pkg: go-simpler.org/slogctx +// pkg: go-simpler.org/slogutil/slogutil // cpu: Apple M1 Pro // BenchmarkHandler/enabled-8 204999273 5.743 ns/op 0 B/op 0 allocs/op // BenchmarkHandler/disabled-8 261334262 4.591 ns/op 0 B/op 0 allocs/op From ababed5018bb2fe38ac698988ab39f5b9e275d3b Mon Sep 17 00:00:00 2001 From: Nick <73077675+tmzane@users.noreply.github.com> Date: Tue, 12 May 2026 11:57:45 +0300 Subject: [PATCH 2/2] fmt --- slogattr/attr_test.go | 2 +- slogctx/handler_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/slogattr/attr_test.go b/slogattr/attr_test.go index 505970d..1654074 100644 --- a/slogattr/attr_test.go +++ b/slogattr/attr_test.go @@ -32,7 +32,7 @@ func TestAll(t *testing.T) { ) var got bytes.Buffer - json.Indent(&got, buf.Bytes(), "", " ") + _ = json.Indent(&got, buf.Bytes(), "", " ") const want = `{ "error": "oops", diff --git a/slogctx/handler_test.go b/slogctx/handler_test.go index 5f98175..5cb405e 100644 --- a/slogctx/handler_test.go +++ b/slogctx/handler_test.go @@ -46,7 +46,7 @@ func foo(ctx context.Context, l *slog.Logger) { func bar(ctx context.Context, l *slog.Logger) { l.InfoContext(ctx, "adding bar") - ctx = slogctx.With(ctx, "bar", 2) + _ = slogctx.With(ctx, "bar", 2) } // goos: darwin