Skip to content

Commit eed5ff9

Browse files
committed
add more tests, add examples, fix bug in handle when withValue hasnt been called yet
1 parent 0f17850 commit eed5ff9

File tree

3 files changed

+244
-28
lines changed

3 files changed

+244
-28
lines changed

context_test.go

+31-27
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,16 @@ func TestWithValue(t *testing.T) {
1818
defer ctx.Done()
1919

2020
ctx = WithValue(ctx, "key1", "value1")
21-
val := ctx.Value(ctxKey("key1"))
22-
//we expect value1 to end up in the context
23-
if val != "value1" {
24-
t.Errorf("Expected value1, got %v", val)
21+
m, ok := ctx.Value(fieldsKey).(*safeMap)
22+
if !ok {
23+
t.Errorf("Expected safeMap to be initialized in context")
2524
}
26-
27-
loggedFields.mu.Lock()
28-
//we expect the key to be in the loggedFields
29-
if loggedFields.fields[0] != "key1" {
30-
t.Errorf("Expected key1, got %v", loggedFields.fields[0])
25+
//we expect value1 to end up in the contexts map
26+
m.mu.Lock()
27+
if m.fields["key1"] != "value1" {
28+
t.Errorf("Expected value1, got %v", m.fields["key1"])
3129
}
32-
loggedFields.mu.Unlock()
33-
ctx.Done()
30+
m.mu.Unlock()
3431
})
3532

3633
t.Run("Will Store Multiple Values On Context", func(t *testing.T) {
@@ -40,15 +37,19 @@ func TestWithValue(t *testing.T) {
4037
ctx = WithValue(ctx, "key2", "value2")
4138
ctx = WithValue(ctx, "key3", "value3")
4239

43-
// we expect value2 and value3 to end up in the context
44-
val := ctx.Value(ctxKey("key2"))
45-
if val != "value2" {
46-
t.Errorf("Expected value2, got %v", val)
40+
m, ok := ctx.Value(fieldsKey).(*safeMap)
41+
if !ok {
42+
t.Errorf("Expected safeMap to be initialized in context")
43+
}
44+
//we expect value2 and value3 to end up in the contexts map
45+
m.mu.Lock()
46+
if m.fields["key2"] != "value2" {
47+
t.Errorf("Expected value2, got %v", m.fields["key2"])
4748
}
48-
val = ctx.Value(ctxKey("key3"))
49-
if val != "value3" {
50-
t.Errorf("Expected value3, got %v", val)
49+
if m.fields["key3"] != "value3" {
50+
t.Errorf("Expected value3, got %v", m.fields["key3"])
5151
}
52+
m.mu.Unlock()
5253
})
5354

5455
t.Run("Will panic with nil parent", func(t *testing.T) {
@@ -60,7 +61,6 @@ func TestWithValue(t *testing.T) {
6061
}
6162

6263
func TestWithValues(t *testing.T) {
63-
6464
t.Run("Will Store Multiple Values On Context", func(t *testing.T) {
6565
ctx := context.Background()
6666
defer ctx.Done()
@@ -76,16 +76,19 @@ func TestWithValues(t *testing.T) {
7676
"complexData": cd,
7777
})
7878

79-
// we expect all the fields to end up in the context
80-
val := ctx.Value(ctxKey("AccountID"))
81-
if val != 987654321 {
82-
t.Errorf("Expected 987654321, got %v", val)
79+
m, ok := ctx.Value(fieldsKey).(*safeMap)
80+
if !ok {
81+
t.Errorf("Expected safeMap to be initialized in context")
82+
}
83+
//we expect value1 to end up in the contexts map
84+
m.mu.Lock()
85+
if m.fields["AccountID"] != 987654321 {
86+
t.Errorf("Expected 987654321, got %v", m.fields["AccountID"])
8387
}
84-
val = ctx.Value(ctxKey("email"))
85-
if val != "[email protected]" {
86-
t.Errorf("Expected [email protected], got %v", val)
88+
if m.fields["email"] != "[email protected]" {
89+
t.Errorf("Expected [email protected], got %v", m.fields["email"])
8790
}
88-
complexVal, ok := ctx.Value(ctxKey("complexData")).(ComplexData)
91+
complexVal, ok := m.fields["complexData"].(ComplexData)
8992
if !ok {
9093
t.Errorf("mistatch type when retrieving ComplexData from context")
9194
}
@@ -101,6 +104,7 @@ func TestWithValues(t *testing.T) {
101104
if complexVal.SliceField[0] != "one" || complexVal.SliceField[1] != "two" || complexVal.SliceField[2] != "three" {
102105
t.Errorf("Expected []string{\"one\", \"two\", \"three\"}, got %v", complexVal.SliceField)
103106
}
107+
m.mu.Unlock()
104108
})
105109

106110
t.Run("Will panic with nil parent", func(t *testing.T) {

handler.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (h Handler) Enabled(ctx context.Context, lvl slog.Level) bool {
2424

2525
// Handle implements slog.Handler.
2626
func (h Handler) Handle(ctx context.Context, r slog.Record) error {
27-
if sm := ctx.Value(fieldsKey).(*safeMap); sm != nil {
27+
if sm, ok := ctx.Value(fieldsKey).(*safeMap); ok {
2828
sm.mu.Lock()
2929
for k, v := range sm.fields {
3030
r.AddAttrs(slog.Any(string(k), v))

handler_test.go

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package ctxslog_test
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"os"
7+
"testing"
8+
9+
"github.com/felttrip/ctxslog"
10+
)
11+
12+
func TestNewHandler(t *testing.T) {
13+
mockHandler := &MockHandler{}
14+
handler := ctxslog.NewHandler(mockHandler)
15+
16+
if handler == nil {
17+
t.Error("NewHandler returned nil")
18+
}
19+
}
20+
21+
func TestHandlerEnabled(t *testing.T) {
22+
mockHandler := &MockHandler{}
23+
handler := ctxslog.NewHandler(mockHandler)
24+
25+
ctx := context.Background()
26+
27+
// Test with different log levels should just default down to the mock handler which is true for all
28+
levels := []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError}
29+
for _, level := range levels {
30+
enabled := handler.Enabled(ctx, level)
31+
if !enabled {
32+
t.Errorf("Handler.Enabled returned false for level %s", level)
33+
}
34+
}
35+
}
36+
37+
func TestHandlerHandle(t *testing.T) {
38+
t.Run("Will Handle when nothing has been added to context", func(t *testing.T) {
39+
mockHandler := &MockHandler{}
40+
handler := ctxslog.NewHandler(mockHandler)
41+
42+
ctx := context.Background()
43+
record := slog.Record{
44+
Level: slog.LevelInfo,
45+
Message: "Test message",
46+
}
47+
48+
err := handler.Handle(ctx, record)
49+
if err != nil {
50+
t.Errorf("Handler.Handle returned an error: %v", err)
51+
}
52+
53+
if !mockHandler.HandleCalled {
54+
t.Error("MockHandler.Handle was not called")
55+
}
56+
57+
if mockHandler.HandleRecord.Message != record.Message || mockHandler.HandleRecord.Level != record.Level {
58+
t.Error("MockHandler.Handle did not receive the correct record")
59+
}
60+
})
61+
t.Run("Will Handle when values have been added to context and add them to attrs", func(t *testing.T) {
62+
mockHandler := &MockHandler{}
63+
handler := ctxslog.NewHandler(mockHandler)
64+
65+
ctx := context.Background()
66+
ctx = ctxslog.WithValue(ctx, "key1", "value1")
67+
ctx = ctxslog.WithValue(ctx, "key2", "value2")
68+
69+
expectedAttrs := []slog.Attr{
70+
{Key: "key1", Value: slog.StringValue("value1")},
71+
{Key: "key2", Value: slog.StringValue("value2")},
72+
}
73+
record := slog.Record{
74+
Level: slog.LevelInfo,
75+
Message: "Test message",
76+
}
77+
78+
err := handler.Handle(ctx, record)
79+
if err != nil {
80+
t.Errorf("Handler.Handle returned an error: %v", err)
81+
}
82+
83+
if !mockHandler.HandleCalled {
84+
t.Error("MockHandler.Handle was not called")
85+
}
86+
87+
if mockHandler.HandleRecord.Message != record.Message || mockHandler.HandleRecord.Level != record.Level {
88+
t.Error("MockHandler.Handle did not receive the correct record")
89+
}
90+
91+
if mockHandler.HandleRecord.NumAttrs() != len(expectedAttrs) {
92+
t.Error("MockHandler.WithAttrs did not receive the correct number of attributes")
93+
}
94+
})
95+
}
96+
97+
type ComplexData struct {
98+
IntField int
99+
StrField string
100+
BoolField bool
101+
SliceField []string
102+
}
103+
104+
func ExampleHandler() {
105+
slog.SetDefault(slog.New(ctxslog.NewHandler(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
106+
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
107+
// Remove time from the output for predictable test output.
108+
if a.Key == slog.TimeKey {
109+
return slog.Attr{}
110+
}
111+
return a
112+
},
113+
}))))
114+
115+
ctx := ctxslog.WithValue(context.Background(), "AccountID", 123456789)
116+
ctx = ctxslog.WithValue(ctx, "email", "[email protected]")
117+
ctx = ctxslog.WithValue(ctx, "sender", "[email protected]")
118+
119+
slog.InfoContext(ctx, "Info With Context")
120+
ctx = ctxslog.WithValues(context.Background(), map[string]interface{}{
121+
"AccountID": 987654321,
122+
"email": "[email protected]",
123+
"complexData": ComplexData{
124+
IntField: 123,
125+
StrField: "DEADBEEF",
126+
BoolField: true,
127+
SliceField: []string{"one", "two", "three"},
128+
},
129+
})
130+
131+
slog.ErrorContext(ctx, "Error With Context")
132+
// Output:
133+
//{"level":"INFO","msg":"Info With Context","AccountID":123456789,"email":"[email protected]","sender":"[email protected]"}
134+
//{"level":"ERROR","msg":"Error With Context","AccountID":987654321,"email":"[email protected]","complexData":{"IntField":123,"StrField":"DEADBEEF","BoolField":true,"SliceField":["one","two","three"]}}
135+
136+
}
137+
138+
func TestHandlerWithAttrs(t *testing.T) {
139+
mockHandler := &MockHandler{}
140+
handler := ctxslog.NewHandler(mockHandler)
141+
142+
attrs := []slog.Attr{
143+
{Key: "attr1", Value: slog.StringValue("value1")},
144+
{Key: "attr2", Value: slog.StringValue("value2")},
145+
}
146+
147+
newHandler := handler.WithAttrs(attrs)
148+
149+
if newHandler == nil {
150+
t.Error("Handler.WithAttrs returned nil")
151+
}
152+
153+
if len(mockHandler.WithAttrsAttrs) != len(attrs) {
154+
t.Error("MockHandler.WithAttrs did not receive the correct number of attributes")
155+
}
156+
157+
for i, attr := range attrs {
158+
if !mockHandler.WithAttrsAttrs[i].Equal(attr) {
159+
t.Errorf("MockHandler.WithAttrs did not receive the correct attribute at index %d", i)
160+
}
161+
}
162+
}
163+
164+
func TestHandlerWithGroup(t *testing.T) {
165+
mockHandler := &MockHandler{WithGroupCalled: false}
166+
handler := ctxslog.NewHandler(mockHandler)
167+
groupName := "test-group"
168+
169+
newHandler := handler.WithGroup(groupName)
170+
171+
if newHandler == nil {
172+
t.Error("Handler.WithGroup returned nil")
173+
}
174+
175+
if mockHandler.WithGroupCalled != true {
176+
t.Error("MockHandler.WithGroup was not called")
177+
}
178+
179+
if mockHandler.WithGroupName != groupName {
180+
t.Error("MockHandler.WithGroup did not receive the correct group name")
181+
}
182+
}
183+
184+
// MockHandler is a mock implementation of slog.Handler for testing purposes.
185+
type MockHandler struct {
186+
HandleCalled bool
187+
HandleRecord slog.Record
188+
WithAttrsAttrs []slog.Attr
189+
WithGroupCalled bool
190+
WithGroupName string
191+
}
192+
193+
func (m *MockHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
194+
return true
195+
}
196+
197+
func (m *MockHandler) Handle(ctx context.Context, r slog.Record) error {
198+
m.HandleCalled = true
199+
m.HandleRecord = r
200+
return nil
201+
}
202+
203+
func (m *MockHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
204+
m.WithAttrsAttrs = attrs
205+
return m
206+
}
207+
208+
func (m *MockHandler) WithGroup(name string) slog.Handler {
209+
m.WithGroupCalled = true
210+
m.WithGroupName = name
211+
return m
212+
}

0 commit comments

Comments
 (0)