1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package zap
22
23 import (
24 "errors"
25 "fmt"
26 "strconv"
27 "testing"
28
29 "go.uber.org/zap/internal/exit"
30 "go.uber.org/zap/internal/ztest"
31 "go.uber.org/zap/zapcore"
32 "go.uber.org/zap/zaptest/observer"
33
34 "github.com/stretchr/testify/assert"
35 "github.com/stretchr/testify/require"
36 )
37
38 func TestSugarWith(t *testing.T) {
39
40 ignored := func(msg interface{}) observer.LoggedEntry {
41 return observer.LoggedEntry{
42 Entry: zapcore.Entry{Level: ErrorLevel, Message: _oddNumberErrMsg},
43 Context: []Field{Any("ignored", msg)},
44 }
45 }
46 nonString := func(pairs ...invalidPair) observer.LoggedEntry {
47 return observer.LoggedEntry{
48 Entry: zapcore.Entry{Level: ErrorLevel, Message: _nonStringKeyErrMsg},
49 Context: []Field{Array("invalid", invalidPairs(pairs))},
50 }
51 }
52 ignoredError := func(err error) observer.LoggedEntry {
53 return observer.LoggedEntry{
54 Entry: zapcore.Entry{Level: ErrorLevel, Message: _multipleErrMsg},
55 Context: []Field{Error(err)},
56 }
57 }
58
59 type withAny func(*SugaredLogger, ...interface{}) *SugaredLogger
60 withMethods := []withAny{(*SugaredLogger).With, (*SugaredLogger).WithLazy}
61
62 tests := []struct {
63 desc string
64 args []interface{}
65 expected []Field
66 errLogs []observer.LoggedEntry
67 }{
68 {
69 desc: "nil args",
70 args: nil,
71 expected: []Field{},
72 errLogs: nil,
73 },
74 {
75 desc: "empty slice of args",
76 args: []interface{}{},
77 expected: []Field{},
78 errLogs: nil,
79 },
80 {
81 desc: "just a dangling key",
82 args: []interface{}{"should ignore"},
83 expected: []Field{},
84 errLogs: []observer.LoggedEntry{ignored("should ignore")},
85 },
86 {
87 desc: "well-formed key-value pairs",
88 args: []interface{}{"foo", 42, "true", "bar"},
89 expected: []Field{Int("foo", 42), String("true", "bar")},
90 errLogs: nil,
91 },
92 {
93 desc: "just a structured field",
94 args: []interface{}{Int("foo", 42)},
95 expected: []Field{Int("foo", 42)},
96 errLogs: nil,
97 },
98 {
99 desc: "structured field and a dangling key",
100 args: []interface{}{Int("foo", 42), "dangling"},
101 expected: []Field{Int("foo", 42)},
102 errLogs: []observer.LoggedEntry{ignored("dangling")},
103 },
104 {
105 desc: "structured field and a dangling non-string key",
106 args: []interface{}{Int("foo", 42), 13},
107 expected: []Field{Int("foo", 42)},
108 errLogs: []observer.LoggedEntry{ignored(13)},
109 },
110 {
111 desc: "key-value pair and a dangling key",
112 args: []interface{}{"foo", 42, "dangling"},
113 expected: []Field{Int("foo", 42)},
114 errLogs: []observer.LoggedEntry{ignored("dangling")},
115 },
116 {
117 desc: "pairs, a structured field, and a dangling key",
118 args: []interface{}{"first", "field", Int("foo", 42), "baz", "quux", "dangling"},
119 expected: []Field{String("first", "field"), Int("foo", 42), String("baz", "quux")},
120 errLogs: []observer.LoggedEntry{ignored("dangling")},
121 },
122 {
123 desc: "one non-string key",
124 args: []interface{}{"foo", 42, true, "bar"},
125 expected: []Field{Int("foo", 42)},
126 errLogs: []observer.LoggedEntry{nonString(invalidPair{2, true, "bar"})},
127 },
128 {
129 desc: "pairs, structured fields, non-string keys, and a dangling key",
130 args: []interface{}{"foo", 42, true, "bar", Int("structure", 11), 42, "reversed", "baz", "quux", "dangling"},
131 expected: []Field{Int("foo", 42), Int("structure", 11), String("baz", "quux")},
132 errLogs: []observer.LoggedEntry{
133 ignored("dangling"),
134 nonString(invalidPair{2, true, "bar"}, invalidPair{5, 42, "reversed"}),
135 },
136 },
137 {
138 desc: "multiple errors",
139 args: []interface{}{errors.New("first"), errors.New("second"), errors.New("third")},
140 expected: []Field{Error(errors.New("first"))},
141 errLogs: []observer.LoggedEntry{
142 ignoredError(errors.New("second")),
143 ignoredError(errors.New("third")),
144 },
145 },
146 }
147
148 for _, tt := range tests {
149 for _, withMethod := range withMethods {
150 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
151 withMethod(logger, tt.args...).Info("")
152 output := logs.AllUntimed()
153 if len(tt.errLogs) > 0 {
154 for i := range tt.errLogs {
155 assert.Equal(t, tt.errLogs[i], output[i], "Unexpected error log at position %d for scenario %s.", i, tt.desc)
156 }
157 }
158 assert.Equal(t, len(tt.errLogs)+1, len(output), "Expected only one non-error message to be logged in scenario %s.", tt.desc)
159 assert.Equal(t, tt.expected, output[len(tt.errLogs)].Context, "Unexpected message context in scenario %s.", tt.desc)
160 })
161 }
162 }
163 }
164
165 func TestSugarWithCaptures(t *testing.T) {
166 type withAny func(*SugaredLogger, ...interface{}) *SugaredLogger
167
168 tests := []struct {
169 name string
170 withMethods []withAny
171 wantJSON []string
172 }{
173 {
174 name: "with captures arguments at time of With",
175 withMethods: []withAny{(*SugaredLogger).With},
176 wantJSON: []string{
177 `{
178 "m": "hello 0",
179 "a0": [0],
180 "b0": [1]
181 }`,
182 `{
183 "m": "world 0",
184 "a0": [0],
185 "c0": [2]
186 }`,
187 },
188 },
189 {
190 name: "lazy with captures arguments at time of Logging",
191 withMethods: []withAny{(*SugaredLogger).WithLazy},
192 wantJSON: []string{
193 `{
194 "m": "hello 0",
195 "a0": [1],
196 "b0": [1]
197 }`,
198 `{
199 "m": "world 0",
200 "a0": [1],
201 "c0": [2]
202 }`,
203 },
204 },
205 }
206
207 for _, tt := range tests {
208 t.Run(tt.name, func(t *testing.T) {
209 enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
210 MessageKey: "m",
211 })
212
213 var bs ztest.Buffer
214 logger := New(zapcore.NewCore(enc, &bs, DebugLevel)).Sugar()
215
216 for i, withMethod := range tt.withMethods {
217 iStr := strconv.Itoa(i)
218 x := 10 * i
219 arr := zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error {
220 enc.AppendInt(x)
221 return nil
222 })
223
224 logger = withMethod(logger, Array("a"+iStr, arr))
225 x++
226 logger.Infow(fmt.Sprintf("hello %d", i), Array("b"+iStr, arr))
227 x++
228 logger = withMethod(logger, Array("c"+iStr, arr))
229 logger.Infow(fmt.Sprintf("world %d", i))
230 }
231
232 if lines := bs.Lines(); assert.Len(t, lines, len(tt.wantJSON)) {
233 for i, want := range tt.wantJSON {
234 assert.JSONEq(t, want, lines[i], "Unexpected output from the %d'th log.", i)
235 }
236 }
237 })
238 }
239 }
240
241 func TestSugaredLoggerLevel(t *testing.T) {
242 levels := []zapcore.Level{
243 DebugLevel,
244 InfoLevel,
245 WarnLevel,
246 ErrorLevel,
247 DPanicLevel,
248 PanicLevel,
249 FatalLevel,
250 }
251
252 for _, lvl := range levels {
253 lvl := lvl
254 t.Run(lvl.String(), func(t *testing.T) {
255 t.Parallel()
256
257 core, _ := observer.New(lvl)
258 log := New(core).Sugar()
259 assert.Equal(t, lvl, log.Level())
260 })
261 }
262
263 t.Run("Nop", func(t *testing.T) {
264 t.Parallel()
265
266 assert.Equal(t, zapcore.InvalidLevel, NewNop().Sugar().Level())
267 })
268 }
269
270 func TestSugarFieldsInvalidPairs(t *testing.T) {
271 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
272 logger.With(42, "foo", []string{"bar"}, "baz").Info("")
273 output := logs.AllUntimed()
274
275
276 require.Equal(t, 2, len(output), "Unexpected number of entries logged.")
277 require.Equal(t, observer.LoggedEntry{Context: []Field{}}, output[1], "Unexpected non-error log entry.")
278
279
280 require.Equal(t, 1, len(output[0].Context), "Expected one field in error entry context.")
281 enc := zapcore.NewMapObjectEncoder()
282 output[0].Context[0].AddTo(enc)
283 assert.Equal(t, []interface{}{
284 map[string]interface{}{"position": int64(0), "key": int64(42), "value": "foo"},
285 map[string]interface{}{"position": int64(2), "key": []interface{}{"bar"}, "value": "baz"},
286 }, enc.Fields["invalid"], "Unexpected output when logging invalid key-value pairs.")
287 })
288 }
289
290 func TestSugarStructuredLogging(t *testing.T) {
291 tests := []struct {
292 msg string
293 expectMsg string
294 }{
295 {"foo", "foo"},
296 {"", ""},
297 }
298
299
300 var (
301 err = errors.New("qux")
302 context = []interface{}{"foo", "bar"}
303 extra = []interface{}{err, "baz", false}
304 expectedFields = []Field{String("foo", "bar"), Error(err), Bool("baz", false)}
305 )
306
307 for _, tt := range tests {
308 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
309 logger.With(context...).Debugw(tt.msg, extra...)
310 logger.With(context...).Infow(tt.msg, extra...)
311 logger.With(context...).Warnw(tt.msg, extra...)
312 logger.With(context...).Errorw(tt.msg, extra...)
313 logger.With(context...).DPanicw(tt.msg, extra...)
314 logger.With(context...).Logw(WarnLevel, tt.msg, extra...)
315
316 expected := make([]observer.LoggedEntry, 6)
317 for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, WarnLevel} {
318 expected[i] = observer.LoggedEntry{
319 Entry: zapcore.Entry{Message: tt.expectMsg, Level: lvl},
320 Context: expectedFields,
321 }
322 }
323 assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
324 })
325 }
326 }
327
328 func TestSugarConcatenatingLogging(t *testing.T) {
329 tests := []struct {
330 args []interface{}
331 expect string
332 }{
333 {[]interface{}{nil}, "<nil>"},
334 }
335
336
337 context := []interface{}{"foo", "bar"}
338 expectedFields := []Field{String("foo", "bar")}
339
340 for _, tt := range tests {
341 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
342 logger.With(context...).Debug(tt.args...)
343 logger.With(context...).Info(tt.args...)
344 logger.With(context...).Warn(tt.args...)
345 logger.With(context...).Error(tt.args...)
346 logger.With(context...).DPanic(tt.args...)
347 logger.With(context...).Log(InfoLevel, tt.args...)
348
349 expected := make([]observer.LoggedEntry, 6)
350 for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, InfoLevel} {
351 expected[i] = observer.LoggedEntry{
352 Entry: zapcore.Entry{Message: tt.expect, Level: lvl},
353 Context: expectedFields,
354 }
355 }
356 assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
357 })
358 }
359 }
360
361 func TestSugarTemplatedLogging(t *testing.T) {
362 tests := []struct {
363 format string
364 args []interface{}
365 expect string
366 }{
367 {"", nil, ""},
368 {"foo", nil, "foo"},
369
370 {"", []interface{}{"foo"}, "foo"},
371 }
372
373
374 context := []interface{}{"foo", "bar"}
375 expectedFields := []Field{String("foo", "bar")}
376
377 for _, tt := range tests {
378 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
379 logger.With(context...).Debugf(tt.format, tt.args...)
380 logger.With(context...).Infof(tt.format, tt.args...)
381 logger.With(context...).Warnf(tt.format, tt.args...)
382 logger.With(context...).Errorf(tt.format, tt.args...)
383 logger.With(context...).DPanicf(tt.format, tt.args...)
384 logger.With(context...).Logf(ErrorLevel, tt.format, tt.args...)
385
386 expected := make([]observer.LoggedEntry, 6)
387 for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, ErrorLevel} {
388 expected[i] = observer.LoggedEntry{
389 Entry: zapcore.Entry{Message: tt.expect, Level: lvl},
390 Context: expectedFields,
391 }
392 }
393 assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
394 })
395 }
396 }
397
398 func TestSugarLnLogging(t *testing.T) {
399 tests := []struct {
400 args []interface{}
401 expect string
402 }{
403 {nil, ""},
404 {[]interface{}{}, ""},
405 {[]interface{}{""}, ""},
406 {[]interface{}{"foo"}, "foo"},
407 {[]interface{}{"foo", "bar"}, "foo bar"},
408 }
409
410
411 context := []interface{}{"foo", "bar"}
412 expectedFields := []Field{String("foo", "bar")}
413
414 for _, tt := range tests {
415 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
416 logger.With(context...).Debugln(tt.args...)
417 logger.With(context...).Infoln(tt.args...)
418 logger.With(context...).Warnln(tt.args...)
419 logger.With(context...).Errorln(tt.args...)
420 logger.With(context...).DPanicln(tt.args...)
421 logger.With(context...).Logln(InfoLevel, tt.args...)
422
423 expected := make([]observer.LoggedEntry, 6)
424 for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, InfoLevel} {
425 expected[i] = observer.LoggedEntry{
426 Entry: zapcore.Entry{Message: tt.expect, Level: lvl},
427 Context: expectedFields,
428 }
429 }
430 assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
431 })
432 }
433 }
434
435 func TestSugarLnLoggingIgnored(t *testing.T) {
436 withSugar(t, WarnLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
437 logger.Infoln("hello")
438 assert.Zero(t, logs.Len(), "Expected zero log statements.")
439 })
440 }
441
442 func TestSugarPanicLogging(t *testing.T) {
443 tests := []struct {
444 loggerLevel zapcore.Level
445 f func(*SugaredLogger)
446 expectedMsg string
447 }{
448 {FatalLevel, func(s *SugaredLogger) { s.Panic("foo") }, ""},
449 {PanicLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"},
450 {DebugLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"},
451 {FatalLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, ""},
452 {PanicLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"},
453 {DebugLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"},
454 {FatalLevel, func(s *SugaredLogger) { s.Panicw("foo") }, ""},
455 {PanicLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"},
456 {DebugLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"},
457 {FatalLevel, func(s *SugaredLogger) { s.Panicln("foo") }, ""},
458 {PanicLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"},
459 {DebugLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"},
460 }
461
462 for _, tt := range tests {
463 withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) {
464 assert.Panics(t, func() { tt.f(sugar) }, "Expected panic-level logger calls to panic.")
465 if tt.expectedMsg != "" {
466 assert.Equal(t, []observer.LoggedEntry{{
467 Context: []Field{},
468 Entry: zapcore.Entry{Message: tt.expectedMsg, Level: PanicLevel},
469 }}, logs.AllUntimed(), "Unexpected log output.")
470 } else {
471 assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.")
472 }
473 })
474 }
475 }
476
477 func TestSugarFatalLogging(t *testing.T) {
478 tests := []struct {
479 loggerLevel zapcore.Level
480 f func(*SugaredLogger)
481 expectedMsg string
482 }{
483 {FatalLevel + 1, func(s *SugaredLogger) { s.Fatal("foo") }, ""},
484 {FatalLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"},
485 {DebugLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"},
486 {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, ""},
487 {FatalLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"},
488 {DebugLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"},
489 {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalw("foo") }, ""},
490 {FatalLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"},
491 {DebugLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"},
492 {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalln("foo") }, ""},
493 {FatalLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"},
494 {DebugLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"},
495 }
496
497 for _, tt := range tests {
498 withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) {
499 stub := exit.WithStub(func() { tt.f(sugar) })
500 assert.True(t, stub.Exited, "Expected all calls to fatal logger methods to exit process.")
501 if tt.expectedMsg != "" {
502 assert.Equal(t, []observer.LoggedEntry{{
503 Context: []Field{},
504 Entry: zapcore.Entry{Message: tt.expectedMsg, Level: FatalLevel},
505 }}, logs.AllUntimed(), "Unexpected log output.")
506 } else {
507 assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.")
508 }
509 })
510 }
511 }
512
513 func TestSugarAddCaller(t *testing.T) {
514 tests := []struct {
515 options []Option
516 pat string
517 }{
518 {opts(AddCaller()), `.+/sugar_test.go:[\d]+$`},
519 {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/sugar_test.go:[\d]+$`},
520 {opts(AddCaller(), AddCallerSkip(1)), `.+/common_test.go:[\d]+$`},
521 {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(5)), `.+/src/runtime/.*:[\d]+$`},
522 }
523 for _, tt := range tests {
524 withSugar(t, DebugLevel, tt.options, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
525 logger.Info("")
526 output := logs.AllUntimed()
527 assert.Equal(t, 1, len(output), "Unexpected number of logs written out.")
528 assert.Regexp(
529 t,
530 tt.pat,
531 output[0].Caller,
532 "Expected to find package name and file name in output.",
533 )
534 })
535 }
536 }
537
538 func TestSugarAddCallerFail(t *testing.T) {
539 errBuf := &ztest.Buffer{}
540 withSugar(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *SugaredLogger, logs *observer.ObservedLogs) {
541 log.Info("Failure.")
542 assert.Regexp(
543 t,
544 `Logger.check error: failed to get caller`,
545 errBuf.String(),
546 "Didn't find expected failure message.",
547 )
548 assert.Equal(
549 t,
550 logs.AllUntimed()[0].Message,
551 "Failure.",
552 "Expected original message to survive failures in runtime.Caller.")
553 })
554 }
555
556 func TestSugarWithOptionsIncreaseLevel(t *testing.T) {
557 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
558 logger = logger.WithOptions(IncreaseLevel(WarnLevel))
559 logger.Info("logger.Info")
560 logger.Warn("logger.Warn")
561 logger.Error("logger.Error")
562 require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.")
563 assert.Equal(
564 t,
565 logs.AllUntimed()[0].Message,
566 "logger.Warn",
567 "Expected first logged message to be warn level message",
568 )
569 })
570 }
571
572 func TestSugarLnWithOptionsIncreaseLevel(t *testing.T) {
573 withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
574 logger = logger.WithOptions(IncreaseLevel(WarnLevel))
575 logger.Infoln("logger.Infoln")
576 logger.Warnln("logger.Warnln")
577 logger.Errorln("logger.Errorln")
578 require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.")
579 assert.Equal(
580 t,
581 logs.AllUntimed()[0].Message,
582 "logger.Warnln",
583 "Expected first logged message to be warn level message",
584 )
585 })
586 }
587
588 func BenchmarkSugarSingleStrArg(b *testing.B) {
589 withSugar(b, InfoLevel, nil , func(log *SugaredLogger, logs *observer.ObservedLogs) {
590 for i := 0; i < b.N; i++ {
591 log.Info("hello world")
592 }
593 })
594 }
595
596 func BenchmarkLnSugarSingleStrArg(b *testing.B) {
597 withSugar(b, InfoLevel, nil , func(log *SugaredLogger, logs *observer.ObservedLogs) {
598 for i := 0; i < b.N; i++ {
599 log.Infoln("hello world")
600 }
601 })
602 }
603
View as plain text