Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -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).
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module go-simpler.org/slogctx
module go-simpler.org/slogutil

go 1.25.0
61 changes: 61 additions & 0 deletions slogattr/attr.go
Original file line number Diff line number Diff line change
@@ -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())
}
74 changes: 74 additions & 0 deletions slogattr/attr_test.go
Original file line number Diff line number Diff line change
@@ -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),
)
}
1 change: 1 addition & 0 deletions context.go → slogctx/context.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package slogctx implements utilities for logging request-scoped data via [context.Context].
package slogctx

import (
Expand Down
File renamed without changes.
14 changes: 7 additions & 7 deletions handler_test.go → slogctx/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -46,12 +46,12 @@ 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
// 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
Expand Down
Loading