23
23
package zapslog
24
24
25
25
import (
26
+ "bytes"
27
+ "encoding/json"
26
28
"log/slog"
29
+ "sync"
27
30
"testing"
31
+ "testing/slogtest"
28
32
"time"
29
33
30
34
"github.com/stretchr/testify/assert"
31
35
"github.com/stretchr/testify/require"
32
36
"go.uber.org/zap/zapcore"
37
+ "go.uber.org/zap/zaptest"
33
38
"go.uber.org/zap/zaptest/observer"
34
39
)
35
40
@@ -128,8 +133,8 @@ func TestEmptyAttr(t *testing.T) {
128
133
}
129
134
130
135
func TestWithName (t * testing.T ) {
131
- t .Parallel ()
132
136
fac , observedLogs := observer .New (zapcore .DebugLevel )
137
+
133
138
t .Run ("default" , func (t * testing.T ) {
134
139
sl := slog .New (NewHandler (fac ))
135
140
sl .Info ("msg" )
@@ -150,6 +155,191 @@ func TestWithName(t *testing.T) {
150
155
})
151
156
}
152
157
158
+ func TestInlineGroup (t * testing.T ) {
159
+ fac , observedLogs := observer .New (zapcore .DebugLevel )
160
+
161
+ t .Run ("simple" , func (t * testing.T ) {
162
+ sl := slog .New (NewHandler (fac ))
163
+ sl .Info ("msg" , "a" , "b" , slog .Group ("" , slog .String ("c" , "d" )), "e" , "f" )
164
+
165
+ logs := observedLogs .TakeAll ()
166
+ require .Len (t , logs , 1 , "Expected exactly one entry to be logged" )
167
+ assert .Equal (t , map [string ]any {
168
+ "a" : "b" ,
169
+ "c" : "d" ,
170
+ "e" : "f" ,
171
+ }, logs [0 ].ContextMap (), "Unexpected context" )
172
+ })
173
+
174
+ t .Run ("recursive" , func (t * testing.T ) {
175
+ sl := slog .New (NewHandler (fac ))
176
+ sl .Info ("msg" , "a" , "b" , slog .Group ("" , slog .Group ("" , slog .Group ("" , slog .String ("c" , "d" ))), slog .Group ("" , "e" , "f" )))
177
+
178
+ logs := observedLogs .TakeAll ()
179
+ require .Len (t , logs , 1 , "Expected exactly one entry to be logged" )
180
+ assert .Equal (t , map [string ]any {
181
+ "a" : "b" ,
182
+ "c" : "d" ,
183
+ "e" : "f" ,
184
+ }, logs [0 ].ContextMap (), "Unexpected context" )
185
+ })
186
+ }
187
+
188
+ func TestWithGroup (t * testing.T ) {
189
+ fac , observedLogs := observer .New (zapcore .DebugLevel )
190
+
191
+ // Groups can be nested inside each other.
192
+ t .Run ("nested" , func (t * testing.T ) {
193
+ sl := slog .New (NewHandler (fac ))
194
+ sl .With ("a" , "b" ).WithGroup ("G" ).WithGroup ("in" ).Info ("msg" , "c" , "d" )
195
+
196
+ logs := observedLogs .TakeAll ()
197
+ require .Len (t , logs , 1 , "Expected exactly one entry to be logged" )
198
+ assert .Equal (t , map [string ]any {
199
+ "G" : map [string ]any {
200
+ "in" : map [string ]any {
201
+ "c" : "d" ,
202
+ },
203
+ },
204
+ "a" : "b" ,
205
+ }, logs [0 ].ContextMap (), "Unexpected context" )
206
+ })
207
+
208
+ t .Run ("nested empty" , func (t * testing.T ) {
209
+ sl := slog .New (NewHandler (fac ))
210
+ sl .With ("a" , "b" ).WithGroup ("G" ).WithGroup ("in" ).Info ("msg" )
211
+
212
+ logs := observedLogs .TakeAll ()
213
+ require .Len (t , logs , 1 , "Expected exactly one entry to be logged" )
214
+ assert .Equal (t , map [string ]any {
215
+ "a" : "b" ,
216
+ }, logs [0 ].ContextMap (), "Unexpected context" )
217
+ })
218
+
219
+ t .Run ("empty group" , func (t * testing.T ) {
220
+ sl := slog .New (NewHandler (fac ))
221
+ sl .With ("a" , "b" ).WithGroup ("G" ).With ("c" , "d" ).WithGroup ("H" ).Info ("msg" )
222
+
223
+ logs := observedLogs .TakeAll ()
224
+ require .Len (t , logs , 1 , "Expected exactly one entry to be logged" )
225
+ assert .Equal (t , map [string ]any {
226
+ "G" : map [string ]any {
227
+ "c" : "d" ,
228
+ },
229
+ "a" : "b" ,
230
+ }, logs [0 ].ContextMap (), "Unexpected context" )
231
+ })
232
+
233
+ t .Run ("skipped field" , func (t * testing.T ) {
234
+ sl := slog .New (NewHandler (fac ))
235
+ sl .WithGroup ("H" ).With (slog.Attr {}).Info ("msg" )
236
+
237
+ logs := observedLogs .TakeAll ()
238
+ require .Len (t , logs , 1 , "Expected exactly one entry to be logged" )
239
+ assert .Equal (t , map [string ]any {}, logs [0 ].ContextMap (), "Unexpected context" )
240
+ })
241
+
242
+ t .Run ("reuse" , func (t * testing.T ) {
243
+ sl := slog .New (NewHandler (fac )).WithGroup ("G" )
244
+
245
+ sl .With ("a" , "b" ).Info ("msg1" , "c" , "d" )
246
+ sl .With ("e" , "f" ).Info ("msg2" , "g" , "h" )
247
+
248
+ logs := observedLogs .TakeAll ()
249
+ require .Len (t , logs , 2 , "Expected exactly two entries to be logged" )
250
+
251
+ assert .Equal (t , map [string ]any {
252
+ "G" : map [string ]any {
253
+ "a" : "b" ,
254
+ "c" : "d" ,
255
+ },
256
+ }, logs [0 ].ContextMap (), "Unexpected context" )
257
+ assert .Equal (t , "msg1" , logs [0 ].Message , "Unexpected message" )
258
+
259
+ assert .Equal (t , map [string ]any {
260
+ "G" : map [string ]any {
261
+ "e" : "f" ,
262
+ "g" : "h" ,
263
+ },
264
+ }, logs [1 ].ContextMap (), "Unexpected context" )
265
+ assert .Equal (t , "msg2" , logs [1 ].Message , "Unexpected message" )
266
+ })
267
+ }
268
+
269
+ // Run a few different loggers with concurrent logs
270
+ // in an attempt to trip up 'go test -race' and discover any data races.
271
+ func TestConcurrentLogs (t * testing.T ) {
272
+ t .Parallel ()
273
+
274
+ const (
275
+ NumWorkers = 10
276
+ NumLogs = 100
277
+ )
278
+
279
+ tests := []struct {
280
+ name string
281
+ buildHandler func (zapcore.Core ) slog.Handler
282
+ }{
283
+ {
284
+ name : "default" ,
285
+ buildHandler : func (core zapcore.Core ) slog.Handler {
286
+ return NewHandler (core )
287
+ },
288
+ },
289
+ {
290
+ name : "grouped" ,
291
+ buildHandler : func (core zapcore.Core ) slog.Handler {
292
+ return NewHandler (core ).WithGroup ("G" )
293
+ },
294
+ },
295
+ {
296
+ name : "named" ,
297
+ buildHandler : func (core zapcore.Core ) slog.Handler {
298
+ return NewHandler (core , WithName ("test-name" ))
299
+ },
300
+ },
301
+ }
302
+
303
+ for _ , tt := range tests {
304
+ tt := tt
305
+ t .Run (tt .name , func (t * testing.T ) {
306
+ t .Parallel ()
307
+
308
+ fac , observedLogs := observer .New (zapcore .DebugLevel )
309
+ sl := slog .New (tt .buildHandler (fac ))
310
+
311
+ // Use two wait groups to coordinate the workers:
312
+ //
313
+ // - ready: indicates when all workers should start logging.
314
+ // - done: indicates when all workers have finished logging.
315
+ var ready , done sync.WaitGroup
316
+ ready .Add (NumWorkers )
317
+ done .Add (NumWorkers )
318
+
319
+ for i := 0 ; i < NumWorkers ; i ++ {
320
+ i := i
321
+ go func () {
322
+ defer done .Done ()
323
+
324
+ ready .Done () // I'm ready.
325
+ ready .Wait () // Are others?
326
+
327
+ for j := 0 ; j < NumLogs ; j ++ {
328
+ sl .Info ("msg" , "worker" , i , "log" , j )
329
+ }
330
+ }()
331
+ }
332
+
333
+ done .Wait ()
334
+
335
+ // Ensure that all logs were recorded.
336
+ logs := observedLogs .TakeAll ()
337
+ assert .Len (t , logs , NumWorkers * NumLogs ,
338
+ "Wrong number of logs recorded" )
339
+ })
340
+ }
341
+ }
342
+
153
343
type Token string
154
344
155
345
func (Token ) LogValue () slog.Value {
@@ -189,3 +379,41 @@ func TestAttrKinds(t *testing.T) {
189
379
},
190
380
entry .ContextMap ())
191
381
}
382
+
383
+ func TestSlogtest (t * testing.T ) {
384
+ var buff bytes.Buffer
385
+ core := zapcore .NewCore (
386
+ zapcore .NewJSONEncoder (zapcore.EncoderConfig {
387
+ TimeKey : slog .TimeKey ,
388
+ MessageKey : slog .MessageKey ,
389
+ LevelKey : slog .LevelKey ,
390
+ EncodeLevel : zapcore .CapitalLevelEncoder ,
391
+ EncodeTime : zapcore .RFC3339TimeEncoder ,
392
+ }),
393
+ zapcore .AddSync (& buff ),
394
+ zapcore .DebugLevel ,
395
+ )
396
+
397
+ // zaptest doesn't expose the underlying core,
398
+ // so we'll extract it from the logger.
399
+ testCore := zaptest .NewLogger (t ).Core ()
400
+
401
+ handler := NewHandler (zapcore .NewTee (core , testCore ))
402
+ err := slogtest .TestHandler (
403
+ handler ,
404
+ func () []map [string ]any {
405
+ // Parse the newline-delimted JSON in buff.
406
+ var entries []map [string ]any
407
+
408
+ dec := json .NewDecoder (bytes .NewReader (buff .Bytes ()))
409
+ for dec .More () {
410
+ var ent map [string ]any
411
+ require .NoError (t , dec .Decode (& ent ), "Error decoding log message" )
412
+ entries = append (entries , ent )
413
+ }
414
+
415
+ return entries
416
+ },
417
+ )
418
+ require .NoError (t , err , "Unexpected error from slogtest.TestHandler" )
419
+ }
0 commit comments