Skip to content

Commit cafa245

Browse files
authored
feat: added "Capture" middleware (#9)
* feat: added "Capture" middleware * feat: added test for handler func in slogt package
1 parent b3d01a1 commit cafa245

10 files changed

+159
-16
lines changed

.golangci.yml

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ linters-settings:
2626
linters:
2727
enable:
2828
- megacheck
29-
- revive
3029
- govet
3130
- unconvert
3231
- unused

accum_test.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,14 @@ import (
1010
"github.com/stretchr/testify/require"
1111
)
1212

13-
type handlerFunc func(ctx context.Context, rec slog.Record) error
14-
15-
func (f handlerFunc) Handle(ctx context.Context, rec slog.Record) error { return f(ctx, rec) }
16-
func (handlerFunc) WithAttrs([]slog.Attr) slog.Handler { return NopHandler() }
17-
func (handlerFunc) WithGroup(string) slog.Handler { return NopHandler() }
18-
func (handlerFunc) Enabled(context.Context, slog.Level) bool { return true }
19-
2013
func TestAccumulator_Handle(t *testing.T) {
2114
t.Run("test", func(t *testing.T) {
2215
lg := slog.New(slogt.Handler(t))
2316
lg.WithGroup("abacaba").Info("test")
2417
})
2518

2619
t.Run("accumulate only attributes", func(t *testing.T) {
27-
acc := Accumulator(handlerFunc(func(ctx context.Context, rec slog.Record) error {
20+
acc := Accumulator(slogt.HandlerFunc(func(ctx context.Context, rec slog.Record) error {
2821
var attrs []slog.Attr
2922
rec.Attrs(func(attr slog.Attr) bool {
3023
attrs = append(attrs, attr)
@@ -53,7 +46,7 @@ func TestAccumulator_Handle(t *testing.T) {
5346
})
5447

5548
t.Run("accumulate groups and attributes", func(t *testing.T) {
56-
acc := Accumulator(handlerFunc(func(ctx context.Context, rec slog.Record) error {
49+
acc := Accumulator(slogt.HandlerFunc(func(ctx context.Context, rec slog.Record) error {
5750
var attrs []slog.Attr
5851
rec.Attrs(func(attr slog.Attr) bool {
5952
attrs = append(attrs, attr)

slogm/capture.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package slogm
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
7+
"github.com/cappuccinotm/slogx"
8+
)
9+
10+
// Capturer is an interface for capturing log records.
11+
type Capturer interface {
12+
Push(slog.Record)
13+
Records() []slog.Record
14+
Close() error
15+
}
16+
17+
// ChannelCapturer is a channel that captures log records.
18+
type ChannelCapturer chan slog.Record
19+
20+
// Push adds a record to the channel.
21+
func (c ChannelCapturer) Push(rec slog.Record) { c <- rec }
22+
23+
// Close closes the channel.
24+
func (c ChannelCapturer) Close() error {
25+
close(c)
26+
return nil
27+
}
28+
29+
// Records returns all captured records for this moment.
30+
func (c ChannelCapturer) Records() []slog.Record {
31+
var records []slog.Record
32+
for len(c) > 0 {
33+
records = append(records, <-c)
34+
}
35+
return records
36+
}
37+
38+
// Capture returns a middleware that captures log records.
39+
func Capture(capt Capturer) slogx.Middleware {
40+
return func(next slogx.HandleFunc) slogx.HandleFunc {
41+
return func(ctx context.Context, rec slog.Record) error {
42+
capt.Push(rec)
43+
return next(ctx, rec)
44+
}
45+
}
46+
}

slogm/capture_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package slogm
2+
3+
import (
4+
"bytes"
5+
"log/slog"
6+
"testing"
7+
8+
"github.com/cappuccinotm/slogx"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestCapture(t *testing.T) {
14+
ch := make(ChannelCapturer, 2)
15+
buf := bytes.NewBuffer(nil)
16+
h := slog.Handler(slog.NewTextHandler(buf, &slog.HandlerOptions{}))
17+
h = slogx.NewChain(h, Capture(ch))
18+
lg := slog.New(h)
19+
lg.Info("test",
20+
slog.String("key", "value"),
21+
slog.Group("g1",
22+
slog.String("a", "1"),
23+
),
24+
)
25+
26+
records := ch.Records()
27+
require.NoError(t, ch.Close())
28+
29+
assert.Equal(t, []slog.Attr{
30+
slog.String("key", "value"),
31+
slog.Group("g1",
32+
slog.String("a", "1"),
33+
),
34+
}, slogx.Attrs(records[0]))
35+
require.NotEmpty(t, buf.String())
36+
}

slogt/testhandler.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package slogt
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
)
7+
8+
// HandlerFunc is a function that implements Handler interface.
9+
// If consumer uses it with slogx.Accumulator, then it can completely capture log records
10+
// and check them in tests.
11+
type HandlerFunc func(ctx context.Context, rec slog.Record) error
12+
13+
func (f HandlerFunc) Handle(ctx context.Context, rec slog.Record) error { return f(ctx, rec) }
14+
func (f HandlerFunc) WithAttrs([]slog.Attr) slog.Handler { return f }
15+
func (f HandlerFunc) WithGroup(string) slog.Handler { return f }
16+
func (f HandlerFunc) Enabled(context.Context, slog.Level) bool { return true }

slogt/testhandler_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package slogt
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestHandlerFunc_Handle(t *testing.T) {
13+
called := 0
14+
f := HandlerFunc(func(ctx context.Context, rec slog.Record) error {
15+
called++
16+
assert.Empty(t, rec)
17+
return nil
18+
})
19+
assert.True(t, f.Enabled(context.Background(), slog.LevelInfo))
20+
require.NoError(t, f.Handle(context.Background(), slog.Record{}))
21+
require.NoError(t, f.WithGroup("group").Handle(context.Background(), slog.Record{}))
22+
require.NoError(t, f.WithAttrs([]slog.Attr{}).Handle(context.Background(), slog.Record{}))
23+
assert.Equal(t, 3, called)
24+
}

slogt/testing.go slogt/thandler.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ package slogt
33

44
import (
55
"fmt"
6-
"strings"
7-
86
"log/slog"
7+
"strings"
98
)
109

1110
type testingOpts struct {

slogt/testing_test.go slogt/thandler_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package slogt
22

33
import (
44
"fmt"
5+
"log/slog"
56
"strings"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
10-
"log/slog"
1111
)
1212

1313
func Test_TestHandler(t *testing.T) {
@@ -19,7 +19,7 @@ func Test_TestHandler(t *testing.T) {
1919
assert.Len(t, tm.rows, 1, "should be 1 row")
2020
assert.Contains(t, tm.rows[0], "t=")
2121
assert.Contains(t, tm.rows[0], fmt.Sprintf(" l=%s", slog.LevelDebug.String()))
22-
assert.Contains(t, tm.rows[0], " s=testing_test.go:17")
22+
assert.Contains(t, tm.rows[0], " s=thandler_test.go:17")
2323
assert.Contains(t, tm.rows[0], fmt.Sprintf(" %s=test", slog.MessageKey))
2424
assert.Contains(t, tm.rows[0], " key=value")
2525

@@ -36,7 +36,7 @@ func Test_TestHandler(t *testing.T) {
3636
assert.Equal(t, "some\nmultiline\nmessage", strings.Join(tm.rows[:3], "\n"))
3737
assert.Contains(t, tm.rows[3], "t=")
3838
assert.Contains(t, tm.rows[3], fmt.Sprintf(" l=%s", slog.LevelDebug.String()))
39-
assert.Contains(t, tm.rows[3], " s=testing_test.go:34")
39+
assert.Contains(t, tm.rows[3], " s=thandler_test.go:34")
4040
assert.Contains(t, tm.rows[3], `msg="message with newlines has been printed to t.Log"`)
4141
})
4242
}

slogx.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package slogx
33

44
import (
55
"context"
6-
76
"log/slog"
87
)
98

@@ -49,6 +48,16 @@ func Error(err error) slog.Attr {
4948
return slog.Any(ErrorKey, err)
5049
}
5150

51+
// Attrs returns attributes from the given record.
52+
func Attrs(rec slog.Record) []slog.Attr {
53+
var attrs []slog.Attr
54+
rec.Attrs(func(attr slog.Attr) bool {
55+
attrs = append(attrs, attr)
56+
return true
57+
})
58+
return attrs
59+
}
60+
5261
// NopHandler returns a slog.Handler, that does nothing.
5362
func NopHandler() slog.Handler { return nopHandler{} }
5463

slogx_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,24 @@ func TestError(t *testing.T) {
3939
})
4040
})
4141
}
42+
43+
func TestAttrs(t *testing.T) {
44+
t.Run("empty", func(t *testing.T) {
45+
rec := slog.Record{}
46+
attrs := Attrs(rec)
47+
assert.Empty(t, attrs)
48+
})
49+
50+
t.Run("non-empty", func(t *testing.T) {
51+
rec := slog.Record{}
52+
rec.AddAttrs(
53+
slog.String("a", "1"),
54+
slog.String("b", "2"),
55+
)
56+
attrs := Attrs(rec)
57+
assert.Equal(t, []slog.Attr{
58+
slog.String("a", "1"),
59+
slog.String("b", "2"),
60+
}, attrs)
61+
})
62+
}

0 commit comments

Comments
 (0)