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
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/setup-go@v6
with:
go-version: 1.21
go-version: 1.22
stable: false
- uses: actions/checkout@v6
- name: golangci-lint
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ jobs:
fail-fast: false
matrix:
go:
- '1.21'
- '1.22'
- '1.23'
- '1.24'
- '1.25'
- '1.26'
- '1.x'
steps:
- uses: actions/checkout@v6
Expand Down
2 changes: 1 addition & 1 deletion examples/failover/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi/examples/failover

go 1.21
go 1.22

require github.com/samber/slog-multi v1.0.0

Expand Down
2 changes: 1 addition & 1 deletion examples/fanout/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi/examples/fanout

go 1.21
go 1.22

require github.com/samber/slog-multi v1.0.0

Expand Down
6 changes: 3 additions & 3 deletions examples/firstmatch/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi/examples/firstmatch

go 1.21
go 1.22

require (
github.com/samber/slog-multi v1.0.0
Expand All @@ -9,8 +9,8 @@ require (

require (
github.com/gorilla/websocket v1.4.2 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/samber/slog-common v0.19.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/slog-common v0.20.0 // indirect
github.com/slack-go/slack v0.12.1 // indirect
golang.org/x/text v0.22.0 // indirect
)
Expand Down
29 changes: 29 additions & 0 deletions examples/firstmatch/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
github.com/samber/slog-slack v1.0.0 h1:HjrlufIPwhRm8Hv4Ox8khu0+0StGEbAEY4fZzOQM5qs=
github.com/samber/slog-slack v1.0.0/go.mod h1:S0UnSLr8Vpv8+gW41dwkg589dUVT+HX2p5Y6zcehSd4=
github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw=
github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2 changes: 1 addition & 1 deletion examples/pipe/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi/examples/pipe

go 1.21
go 1.22

require (
github.com/samber/lo v1.47.0
Expand Down
2 changes: 1 addition & 1 deletion examples/pool/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi/examples/pool

go 1.21
go 1.22

require github.com/samber/slog-multi v1.0.0

Expand Down
6 changes: 3 additions & 3 deletions examples/router/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi/examples/router

go 1.21
go 1.22

require (
github.com/samber/slog-multi v1.0.0
Expand All @@ -9,8 +9,8 @@ require (

require (
github.com/gorilla/websocket v1.4.2 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/samber/slog-common v0.19.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/samber/slog-common v0.20.0 // indirect
github.com/slack-go/slack v0.12.1 // indirect
golang.org/x/text v0.22.0 // indirect
)
Expand Down
12 changes: 6 additions & 6 deletions examples/router/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
github.com/samber/slog-slack v1.0.0 h1:HjrlufIPwhRm8Hv4Ox8khu0+0StGEbAEY4fZzOQM5qs=
github.com/samber/slog-slack v1.0.0/go.mod h1:S0UnSLr8Vpv8+gW41dwkg589dUVT+HX2p5Y6zcehSd4=
github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw=
github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
Expand Down
202 changes: 202 additions & 0 deletions fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package slogmulti

import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func buildFuzzRecord(levelInt int, msg string, attrCount int) slog.Record {
level := slog.Level(levelInt)
r := slog.NewRecord(time.Now(), level, msg, 0)
for i := 0; i < attrCount; i++ {
r.AddAttrs(slog.Int(fmt.Sprintf("k%d", i), i))
}
return r
}

func FuzzFanoutHandle(f *testing.F) {
f.Add(0, "hello", 3)
f.Add(-4, "", 0)
f.Add(4, "warn msg", 1)
f.Add(8, strings.Repeat("x", 1000), 50)

f.Fuzz(func(t *testing.T, levelInt int, msg string, attrCount int) {
if attrCount < 0 {
attrCount = 0
}
if attrCount > 50 {
attrCount = 50
}

level := slog.Level(levelInt)
h1 := newCountingHandler(slog.LevelDebug)
h2 := newCountingHandler(slog.LevelWarn)
h3 := newCountingHandler(slog.LevelDebug)

fanout := Fanout(h1, h2, h3)
r := buildFuzzRecord(levelInt, msg, attrCount)
err := fanout.Handle(context.Background(), r)
assert.NoError(t, err)

// Fanout only calls Handle on enabled handlers
var expected1, expected2, expected3 int64
if level >= slog.LevelDebug {
expected1 = 1
expected3 = 1
}
if level >= slog.LevelWarn {
expected2 = 1
}

assert.Equal(t, expected1, h1.handleCount.Load(), "h1")
assert.Equal(t, expected2, h2.handleCount.Load(), "h2")
assert.Equal(t, expected3, h3.handleCount.Load(), "h3")
})
}

func FuzzFailoverHandle(f *testing.F) {
f.Add(0, "test", true, false)
f.Add(0, "test", false, false)
f.Add(0, "test", true, true)
f.Add(4, "", false, true)

f.Fuzz(func(t *testing.T, levelInt int, msg string, firstFails bool, secondFails bool) {
// errorHandler.Enabled always returns true, countingHandler checks minLevel.
// Use errorHandler for both fail and success paths so Enabled is always true,
// avoiding level-based skipping that complicates assertions.
h1err := &errorHandler{err: errors.New("h1 fail")}
h1ok := &errorHandler{err: nil}
h2err := &errorHandler{err: errors.New("h2 fail")}
h2ok := &errorHandler{err: nil}

var h1, h2 *errorHandler
if firstFails {
h1 = h1err
} else {
h1 = h1ok
}
if secondFails {
h2 = h2err
} else {
h2 = h2ok
}

handler := Failover()(h1, h2)
r := buildFuzzRecord(levelInt, msg, 0)
err := handler.Handle(context.Background(), r)

if !firstFails {
assert.NoError(t, err)
assert.Equal(t, int64(1), h1.handleCount.Load())
assert.Equal(t, int64(0), h2.handleCount.Load())
} else if !secondFails {
assert.NoError(t, err)
assert.Equal(t, int64(1), h2.handleCount.Load())
} else {
assert.Error(t, err)
}
})
}

func FuzzPoolHandle(f *testing.F) {
f.Add(0, "hello")
f.Add(-4, "")
f.Add(8, strings.Repeat("a", 500))

f.Fuzz(func(t *testing.T, levelInt int, msg string) {
// Use errorHandler (always enabled) to avoid level-based skipping
handlers := make([]*errorHandler, 3)
slogHandlers := make([]slog.Handler, 3)
for i := range handlers {
handlers[i] = &errorHandler{err: nil}
slogHandlers[i] = handlers[i]
}

pool := Pool()(slogHandlers...)

const iterations = 100
for i := 0; i < iterations; i++ {
r := buildFuzzRecord(levelInt, msg, 0)
err := pool.Handle(context.Background(), r)
assert.NoError(t, err)
}

var total int64
for _, h := range handlers {
total += h.handleCount.Load()
}
assert.Equal(t, int64(iterations), total)
})
}

func FuzzFirstMatchHandle(f *testing.F) {
f.Add(0, "hello world")
f.Add(4, "error occurred")
f.Add(-4, "debug")
f.Add(8, "")

f.Fuzz(func(t *testing.T, levelInt int, msg string) {
// Use errorHandler (always enabled) as sinks to avoid level gating
errorSink := &errorHandler{err: nil}
infoSink := &errorHandler{err: nil}
catchAll := &errorHandler{err: nil}

handler := Router().
Add(errorSink, LevelIs(slog.LevelError)).
Add(infoSink, LevelIs(slog.LevelInfo)).
Add(catchAll).
FirstMatch().
Handler()

r := buildFuzzRecord(levelInt, msg, 0)
err := handler.Handle(context.Background(), r)
assert.NoError(t, err)

level := slog.Level(levelInt)
total := errorSink.handleCount.Load() + infoSink.handleCount.Load() + catchAll.handleCount.Load()

switch level {
case slog.LevelError:
assert.Equal(t, int64(1), errorSink.handleCount.Load())
assert.Equal(t, int64(1), total)
case slog.LevelInfo:
assert.Equal(t, int64(1), infoSink.handleCount.Load())
assert.Equal(t, int64(1), total)
default:
// Catch-all gets it (no predicate, always matches)
assert.Equal(t, int64(1), catchAll.handleCount.Load())
assert.Equal(t, int64(1), total)
}
})
}

func FuzzRouterPredicates(f *testing.F) {
f.Add("hello world", 0)
f.Add("", -4)
f.Add("error in database", 8)
f.Add(strings.Repeat("x", 1000), 4)

f.Fuzz(func(t *testing.T, msg string, levelInt int) {
level := slog.Level(levelInt)
r := slog.NewRecord(time.Now(), level, msg, 0)
r.AddAttrs(slog.String("key", "value"), slog.Int("num", 42))
ctx := context.Background()

// All of these must not panic regardless of input
LevelIs(slog.LevelInfo, slog.LevelError)(ctx, r)
LevelIsNot(slog.LevelInfo)(ctx, r)
MessageIs(msg)(ctx, r)
MessageIsNot(msg)(ctx, r)
MessageContains("error")(ctx, r)
MessageNotContains("error")(ctx, r)
AttrValueIs("key", "value")(ctx, r)
AttrKindIs("key", slog.KindString)(ctx, r)
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/slog-multi

go 1.21
go 1.22

require (
github.com/samber/lo v1.52.0
Expand Down
2 changes: 1 addition & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
go 1.21
go 1.22

use (
.
Expand Down
Loading
Loading