-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogger_addwriter_test.go
More file actions
323 lines (256 loc) · 8.01 KB
/
Copy pathlogger_addwriter_test.go
File metadata and controls
323 lines (256 loc) · 8.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package velocity
import (
"bytes"
"sync"
"sync/atomic"
"testing"
"time"
)
// TestLogger_AddWriter verifies that a writer receives log entries.
func TestLogger_AddWriter(t *testing.T) {
var buf bytes.Buffer
logger := New(WithConsoleOutput(&buf))
var callCount atomic.Int64
trackingWriterFn := WriterFunc(func(_ *Entry) error {
callCount.Add(1)
// NOTE: Don't call Release() here - MultiWriter.writerWorker() does that
return nil
})
logger.AddWriter("tracker", &trackingWriterFn)
logger.Info("test message 1")
logger.Warn("test message 2")
waitFor(t, func() bool {
return callCount.Load() >= 1
}, 200*time.Millisecond, 10*time.Millisecond, "Expected at least 1 call to writer")
}
// TestLogger_AddWriter_Concurrent verifies thread-safety of AddWriter.
func TestLogger_AddWriter_Concurrent(t *testing.T) {
var buf bytes.Buffer
logger := New(WithConsoleOutput(&buf))
var wg sync.WaitGroup
for i := range 10 {
wg.Add(1)
go func(_ int) {
defer wg.Done()
writerFn := WriterFunc(func(_ *Entry) error {
// NOTE: MultiWriter handles Release()
return nil
})
logger.AddWriter("writer", &writerFn)
// Intentional delay: pacing concurrent AddWriter calls
time.Sleep(10 * time.Millisecond)
}(i)
}
wg.Wait()
logger.Info("concurrency test")
// Allow async processing - minimal wait for message to be processed
waitFor(t, func() bool {
return true // Just verify no deadlock/crash
}, 100*time.Millisecond, 10*time.Millisecond, "Concurrent writes should complete")
}
// TestLogger_RemoveWriter verifies that a removed writer no longer receives entries.
func TestLogger_RemoveWriter(t *testing.T) {
var buf bytes.Buffer
logger := New(WithConsoleOutput(&buf))
var count1 atomic.Int64
var count2 atomic.Int64
writer1Fn := WriterFunc(func(_ *Entry) error {
count1.Add(1)
// NOTE: MultiWriter handles Release()
return nil
})
writer2Fn := WriterFunc(func(_ *Entry) error {
count2.Add(1)
// NOTE: MultiWriter handles Release()
return nil
})
logger.AddWriter("writer1", &writer1Fn)
logger.AddWriter("writer2", &writer2Fn)
for range 5 {
logger.Info("message")
}
waitFor(t, func() bool {
return count1.Load() >= 5 && count2.Load() >= 5
}, 200*time.Millisecond, 10*time.Millisecond, "Both writers should receive initial messages")
initial1 := count1.Load()
initial2 := count2.Load()
logger.RemoveWriter("writer1")
for range 5 {
logger.Info("message")
}
waitFor(t, func() bool {
return count2.Load() >= initial2+5
}, 200*time.Millisecond, 10*time.Millisecond, "Active writer should receive additional messages")
final1 := count1.Load()
final2 := count2.Load()
if final1 > initial1 {
t.Errorf("Removed writer received entries: %d -> %d", initial1, final1)
}
if final2 <= initial2 {
t.Errorf("Active writer did not receive entries: %d -> %d", initial2, final2)
}
}
// TestLogger_AddWriter_NilSafety verifies nil-safety of AddWriter/RemoveWriter.
func TestLogger_AddWriter_NilSafety(t *testing.T) {
var nilLogger *Logger
nilLogger.AddWriter("test", &NoOpWriter{})
nilLogger.RemoveWriter("test")
logger := newForTesting(nil)
logger.AddWriter("noop", &NoOpWriter{})
logger.Info("test message")
logger.RemoveWriter("noop")
// Allow async processing to complete
waitFor(t, func() bool {
return true // Verify no deadlock/crash
}, 100*time.Millisecond, 10*time.Millisecond, "Operations should complete without deadlock")
}
// TestLogger_AddWriter_MultipleWriters verifies multiple writers receive entries.
func TestLogger_AddWriter_MultipleWriters(t *testing.T) {
var buf bytes.Buffer
logger := New(WithConsoleOutput(&buf))
var count1 atomic.Int64
var count2 atomic.Int64
var count3 atomic.Int64
writer1Fn := WriterFunc(func(_ *Entry) error {
count1.Add(1)
// NOTE: MultiWriter handles Release()
return nil
})
writer2Fn := WriterFunc(func(_ *Entry) error {
count2.Add(1)
// NOTE: MultiWriter handles Release()
return nil
})
writer3Fn := WriterFunc(func(_ *Entry) error {
count3.Add(1)
// NOTE: MultiWriter handles Release()
return nil
})
logger.AddWriter("w1", &writer1Fn)
logger.AddWriter("w2", &writer2Fn)
logger.AddWriter("w3", &writer3Fn)
for range 10 {
logger.Info("message")
}
waitFor(t, func() bool {
return count1.Load() >= 10 && count2.Load() >= 10 && count3.Load() >= 10
}, 300*time.Millisecond, 10*time.Millisecond, "All writers should receive messages")
c1 := count1.Load()
c2 := count2.Load()
c3 := count3.Load()
if c1 == 0 {
t.Errorf("Writer 1 received no entries")
}
if c2 == 0 {
t.Errorf("Writer 2 received no entries")
}
if c3 == 0 {
t.Errorf("Writer 3 received no entries")
}
t.Logf("Writers received: w1=%d, w2=%d, w3=%d", c1, c2, c3)
}
// TestLogger_AddWriter_LevelFiltering verifies writers respect level filtering.
func TestLogger_AddWriter_LevelFiltering(t *testing.T) {
var buf bytes.Buffer
logger := New(
WithConsoleOutput(&buf),
WithLevel(LevelInfo),
)
var debugCount atomic.Int64
var infoCount atomic.Int64
trackingWriterFn := WriterFunc(func(e *Entry) error {
switch e.Level {
case LevelDebug:
debugCount.Add(1)
case LevelInfo:
infoCount.Add(1)
case LevelWarn, LevelError, LevelFatal, LevelOff:
// not tracked
}
// NOTE: MultiWriter handles Release()
return nil
})
logger.AddWriter("tracker", &trackingWriterFn)
logger.Debug("debug message")
logger.Info("info message")
logger.Warn("warn message")
waitFor(t, func() bool {
return infoCount.Load() >= 1
}, 200*time.Millisecond, 10*time.Millisecond, "Info writer should receive entries")
debug := debugCount.Load()
info := infoCount.Load()
t.Logf("Received counts: debug=%d, info=%d", debug, info)
if info == 0 {
t.Error("Info writer received no entries")
}
}
// TestLogger_AddWriter_EntryIntegrity verifies entry fields are preserved.
func TestLogger_AddWriter_EntryIntegrity(t *testing.T) {
var buf bytes.Buffer
logger := New(WithConsoleOutput(&buf))
// Use a channel to receive the entry data safely
type entryData struct {
message string
level Level
fields int
}
resultCh := make(chan entryData, 1)
trackingWriterFn := WriterFunc(func(e *Entry) error {
// Extract data immediately before Release
data := entryData{
message: e.Message,
level: e.Level,
fields: len(e.Fields),
}
resultCh <- data
// NOTE: MultiWriter handles Release()
return nil
})
logger.AddWriter("tracker", &trackingWriterFn)
testMsg := "integrity test message"
testField := String("component", "test")
logger.Info(testMsg, testField)
// Wait for the result with timeout
select {
case data := <-resultCh:
if data.message != testMsg {
t.Errorf("Message mismatch: got %q, want %q", data.message, testMsg)
}
if data.level != LevelInfo {
t.Errorf("Level mismatch: got %v, want %v", data.level, LevelInfo)
}
if data.fields != 1 {
t.Errorf("Field count mismatch: got %d, want 1", data.fields)
}
case <-time.After(500 * time.Millisecond):
t.Error("Timeout waiting for log entry")
}
}
func TestLogger_NilSetLevel(_ *testing.T) {
var l *Logger
l.SetLevel(LevelDebug) // must not panic
_ = l.Level() // must not panic
}
// TestLogger_ChildSeesWriterAddedAfterCreation is a regression test for the bug where
// child loggers created via With() did not see writers added to the parent after the
// child was created. The shared writerSet pointer must propagate the new writer to
// all members of the logger family.
func TestLogger_ChildSeesWriterAddedAfterCreation(t *testing.T) {
t.Parallel()
parent := New(WithConsoleOutput(&bytes.Buffer{}))
// Create a child before adding any writer.
child := parent.With(String("child", "true"))
var count atomic.Int64
fn := WriterFunc(func(_ *Entry) error {
count.Add(1)
return nil
})
// Add the writer to the parent AFTER the child was created.
parent.AddWriter("tracker", &fn)
// Log through the child — the shared writerSet means the tracker should fire.
child.Info("from child")
waitFor(t, func() bool {
return count.Load() >= 1
}, 300*time.Millisecond, 10*time.Millisecond,
"child should route through writer added to parent after child creation")
}