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 "sync"
28 "sync/atomic"
29 "testing"
30
31 "go.uber.org/zap/internal/exit"
32 "go.uber.org/zap/internal/ztest"
33 "go.uber.org/zap/zapcore"
34 "go.uber.org/zap/zaptest/observer"
35
36 "github.com/stretchr/testify/assert"
37 "github.com/stretchr/testify/require"
38 )
39
40 func makeCountingHook() (func(zapcore.Entry) error, *atomic.Int64) {
41 count := &atomic.Int64{}
42 h := func(zapcore.Entry) error {
43 count.Add(1)
44 return nil
45 }
46 return h, count
47 }
48
49 func TestLoggerAtomicLevel(t *testing.T) {
50
51 dl := NewAtomicLevel()
52
53 withLogger(t, dl, nil, func(grandparent *Logger, _ *observer.ObservedLogs) {
54 parent := grandparent.With(Int("generation", 1))
55 child := parent.With(Int("generation", 2))
56
57 tests := []struct {
58 setLevel zapcore.Level
59 testLevel zapcore.Level
60 enabled bool
61 }{
62 {DebugLevel, DebugLevel, true},
63 {InfoLevel, DebugLevel, false},
64 {WarnLevel, PanicLevel, true},
65 }
66
67 for _, tt := range tests {
68 dl.SetLevel(tt.setLevel)
69 for _, logger := range []*Logger{grandparent, parent, child} {
70 if tt.enabled {
71 assert.NotNil(
72 t,
73 logger.Check(tt.testLevel, ""),
74 "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel,
75 )
76 } else {
77 assert.Nil(
78 t,
79 logger.Check(tt.testLevel, ""),
80 "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel,
81 )
82 }
83 }
84 }
85 })
86 }
87
88 func TestLoggerLevel(t *testing.T) {
89 levels := []zapcore.Level{
90 DebugLevel,
91 InfoLevel,
92 WarnLevel,
93 ErrorLevel,
94 DPanicLevel,
95 PanicLevel,
96 FatalLevel,
97 }
98
99 for _, lvl := range levels {
100 lvl := lvl
101 t.Run(lvl.String(), func(t *testing.T) {
102 t.Parallel()
103
104 core, _ := observer.New(lvl)
105 log := New(core)
106 assert.Equal(t, lvl, log.Level())
107 })
108 }
109
110 t.Run("Nop", func(t *testing.T) {
111 assert.Equal(t, zapcore.InvalidLevel, NewNop().Level())
112 })
113 }
114
115 func TestLoggerInitialFields(t *testing.T) {
116 fieldOpts := opts(Fields(Int("foo", 42), String("bar", "baz")))
117 withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) {
118 logger.Info("")
119 assert.Equal(
120 t,
121 observer.LoggedEntry{Context: []Field{Int("foo", 42), String("bar", "baz")}},
122 logs.AllUntimed()[0],
123 "Unexpected output with initial fields set.",
124 )
125 })
126 }
127
128 func TestLoggerWith(t *testing.T) {
129 tests := []struct {
130 name string
131 initialFields []Field
132 withMethod func(*Logger, ...Field) *Logger
133 }{
134 {
135 "regular non lazy logger",
136 []Field{Int("foo", 42)},
137 (*Logger).With,
138 },
139 {
140 "regular non lazy logger no initial fields",
141 []Field{},
142 (*Logger).With,
143 },
144 {
145 "lazy with logger",
146 []Field{Int("foo", 42)},
147 (*Logger).WithLazy,
148 },
149 {
150 "lazy with logger no initial fields",
151 []Field{},
152 (*Logger).WithLazy,
153 },
154 }
155 for _, tt := range tests {
156 t.Run(tt.name, func(t *testing.T) {
157 withLogger(t, DebugLevel, opts(Fields(tt.initialFields...)), func(logger *Logger, logs *observer.ObservedLogs) {
158
159
160 tt.withMethod(logger).Info("")
161 tt.withMethod(logger, String("one", "two")).Info("")
162 tt.withMethod(logger, String("three", "four")).Info("")
163 tt.withMethod(logger, String("five", "six")).With(String("seven", "eight")).Info("")
164 logger.Info("")
165
166 assert.Equal(t, []observer.LoggedEntry{
167 {Context: tt.initialFields},
168 {Context: append(tt.initialFields, String("one", "two"))},
169 {Context: append(tt.initialFields, String("three", "four"))},
170 {Context: append(tt.initialFields, String("five", "six"), String("seven", "eight"))},
171 {Context: tt.initialFields},
172 }, logs.AllUntimed(), "Unexpected cross-talk between child loggers.")
173 })
174 })
175 }
176 }
177
178 func TestLoggerWithCaptures(t *testing.T) {
179 type withF func(*Logger, ...Field) *Logger
180 tests := []struct {
181 name string
182 withMethods []withF
183 wantJSON []string
184 }{
185 {
186 name: "regular with captures arguments at time of With",
187 withMethods: []withF{(*Logger).With},
188 wantJSON: []string{
189 `{
190 "m": "hello 0",
191 "a0": [0],
192 "b0": [1]
193 }`,
194 `{
195 "m": "world 0",
196 "a0": [0],
197 "c0": [2]
198 }`,
199 },
200 },
201 {
202 name: "lazy with captures arguments at time of With or Logging",
203 withMethods: []withF{(*Logger).WithLazy},
204 wantJSON: []string{
205 `{
206 "m": "hello 0",
207 "a0": [1],
208 "b0": [1]
209 }`,
210 `{
211 "m": "world 0",
212 "a0": [1],
213 "c0": [2]
214 }`,
215 },
216 },
217 {
218 name: "2x With captures arguments at time of each With",
219 withMethods: []withF{(*Logger).With, (*Logger).With},
220 wantJSON: []string{
221 `{
222 "m": "hello 0",
223 "a0": [0],
224 "b0": [1]
225 }`,
226 `{
227 "m": "world 0",
228 "a0": [0],
229 "c0": [2]
230 }`,
231 `{
232 "m": "hello 1",
233 "a0": [0],
234 "c0": [2],
235 "a1": [10],
236 "b1": [11]
237 }`,
238 `{
239 "m": "world 1",
240 "a0": [0],
241 "c0": [2],
242 "a1": [10],
243 "c1": [12]
244 }`,
245 },
246 },
247 {
248 name: "2x WithLazy. Captures arguments only at logging time.",
249 withMethods: []withF{(*Logger).WithLazy, (*Logger).WithLazy},
250 wantJSON: []string{
251 `{
252 "m": "hello 0",
253 "a0": [1],
254 "b0": [1]
255 }`,
256 `{
257 "m": "world 0",
258 "a0": [1],
259 "c0": [2]
260 }`,
261 `{
262 "m": "hello 1",
263 "a0": [1],
264 "c0": [2],
265 "a1": [11],
266 "b1": [11]
267 }`,
268 `{
269 "m": "world 1",
270 "a0": [1],
271 "c0": [2],
272 "a1": [11],
273 "c1": [12]
274 }`,
275 },
276 },
277 {
278 name: "WithLazy then With",
279 withMethods: []withF{(*Logger).WithLazy, (*Logger).With},
280 wantJSON: []string{
281 `{
282 "m": "hello 0",
283 "a0": [1],
284 "b0": [1]
285 }`,
286 `{
287 "m": "world 0",
288 "a0": [1],
289 "c0": [2]
290 }`,
291 `{
292 "m": "hello 1",
293 "a0": [1],
294 "c0": [2],
295 "a1": [10],
296 "b1": [11]
297 }`,
298 `{
299 "m": "world 1",
300 "a0": [1],
301 "c0": [2],
302 "a1": [10],
303 "c1": [12]
304 }`,
305 },
306 },
307 {
308 name: "With then WithLazy",
309 withMethods: []withF{(*Logger).With, (*Logger).WithLazy},
310 wantJSON: []string{
311 `{
312 "m": "hello 0",
313 "a0": [0],
314 "b0": [1]
315 }`,
316 `{
317 "m": "world 0",
318 "a0": [0],
319 "c0": [2]
320 }`,
321 `{
322 "m": "hello 1",
323 "a0": [0],
324 "c0": [2],
325 "a1": [11],
326 "b1": [11]
327 }`,
328 `{
329 "m": "world 1",
330 "a0": [0],
331 "c0": [2],
332 "a1": [11],
333 "c1": [12]
334 }`,
335 },
336 },
337 }
338
339 for _, tt := range tests {
340 t.Run(tt.name, func(t *testing.T) {
341 enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
342 MessageKey: "m",
343 })
344
345 var bs ztest.Buffer
346 logger := New(zapcore.NewCore(enc, &bs, DebugLevel))
347
348 for i, withMethod := range tt.withMethods {
349
350 iStr := strconv.Itoa(i)
351 x := 10 * i
352 arr := zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error {
353 enc.AppendInt(x)
354 return nil
355 })
356
357
358 logger = withMethod(logger, Array("a"+iStr, arr))
359 x++
360 logger.Info(fmt.Sprintf("hello %d", i), Array("b"+iStr, arr))
361 x++
362 logger = withMethod(logger, Array("c"+iStr, arr))
363 logger.Info(fmt.Sprintf("world %d", i))
364 }
365
366 if lines := bs.Lines(); assert.Len(t, lines, len(tt.wantJSON)) {
367 for i, want := range tt.wantJSON {
368 assert.JSONEq(t, want, lines[i], "Unexpected output from the %d'th log.", i)
369 }
370 }
371 })
372 }
373 }
374
375 func TestLoggerLogPanic(t *testing.T) {
376 for _, tt := range []struct {
377 do func(*Logger)
378 should bool
379 expected string
380 }{
381 {func(logger *Logger) { logger.Check(PanicLevel, "foo").Write() }, true, "foo"},
382 {func(logger *Logger) { logger.Log(PanicLevel, "bar") }, true, "bar"},
383 {func(logger *Logger) { logger.Panic("baz") }, true, "baz"},
384 } {
385 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
386 if tt.should {
387 assert.Panics(t, func() { tt.do(logger) }, "Expected panic")
388 } else {
389 assert.NotPanics(t, func() { tt.do(logger) }, "Expected no panic")
390 }
391
392 output := logs.AllUntimed()
393 assert.Equal(t, 1, len(output), "Unexpected number of logs.")
394 assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.")
395 assert.Equal(
396 t,
397 zapcore.Entry{Message: tt.expected, Level: PanicLevel},
398 output[0].Entry,
399 "Unexpected output from panic-level Log.",
400 )
401 })
402 }
403 }
404
405 func TestLoggerLogFatal(t *testing.T) {
406 for _, tt := range []struct {
407 do func(*Logger)
408 expected string
409 }{
410 {func(logger *Logger) { logger.Check(FatalLevel, "foo").Write() }, "foo"},
411 {func(logger *Logger) { logger.Log(FatalLevel, "bar") }, "bar"},
412 {func(logger *Logger) { logger.Fatal("baz") }, "baz"},
413 } {
414 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
415 stub := exit.WithStub(func() {
416 tt.do(logger)
417 })
418 assert.True(t, stub.Exited, "Expected Fatal logger call to terminate process.")
419 output := logs.AllUntimed()
420 assert.Equal(t, 1, len(output), "Unexpected number of logs.")
421 assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.")
422 assert.Equal(
423 t,
424 zapcore.Entry{Message: tt.expected, Level: FatalLevel},
425 output[0].Entry,
426 "Unexpected output from fatal-level Log.",
427 )
428 })
429 }
430 }
431
432 func TestLoggerLeveledMethods(t *testing.T) {
433 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
434 tests := []struct {
435 method func(string, ...Field)
436 expectedLevel zapcore.Level
437 }{
438 {logger.Debug, DebugLevel},
439 {logger.Info, InfoLevel},
440 {logger.Warn, WarnLevel},
441 {logger.Error, ErrorLevel},
442 {logger.DPanic, DPanicLevel},
443 }
444 for i, tt := range tests {
445 tt.method("")
446 output := logs.AllUntimed()
447 assert.Equal(t, i+1, len(output), "Unexpected number of logs.")
448 assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.")
449 assert.Equal(
450 t,
451 zapcore.Entry{Level: tt.expectedLevel},
452 output[i].Entry,
453 "Unexpected output from %s-level logger method.", tt.expectedLevel)
454 }
455 })
456 }
457
458 func TestLoggerLogLevels(t *testing.T) {
459 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
460 levels := []zapcore.Level{
461 DebugLevel,
462 InfoLevel,
463 WarnLevel,
464 ErrorLevel,
465 DPanicLevel,
466 }
467 for i, level := range levels {
468 logger.Log(level, "")
469 output := logs.AllUntimed()
470 assert.Equal(t, i+1, len(output), "Unexpected number of logs.")
471 assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.")
472 assert.Equal(
473 t,
474 zapcore.Entry{Level: level},
475 output[i].Entry,
476 "Unexpected output from %s-level logger method.", level)
477 }
478 })
479 }
480
481 func TestLoggerAlwaysPanics(t *testing.T) {
482
483
484 withLogger(t, FatalLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
485 msg := "Even if output is disabled, logger.Panic should always panic."
486 assert.Panics(t, func() { logger.Panic("foo") }, msg)
487 assert.Panics(t, func() { logger.Log(PanicLevel, "foo") }, msg)
488 assert.Panics(t, func() {
489 if ce := logger.Check(PanicLevel, "foo"); ce != nil {
490 ce.Write()
491 }
492 }, msg)
493 assert.Equal(t, 0, logs.Len(), "Panics shouldn't be written out if PanicLevel is disabled.")
494 })
495 }
496
497 func TestLoggerAlwaysFatals(t *testing.T) {
498
499
500 withLogger(t, FatalLevel+1, nil, func(logger *Logger, logs *observer.ObservedLogs) {
501 stub := exit.WithStub(func() { logger.Fatal("") })
502 assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.")
503
504 stub = exit.WithStub(func() { logger.Log(FatalLevel, "") })
505 assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.")
506
507 stub = exit.WithStub(func() {
508 if ce := logger.Check(FatalLevel, ""); ce != nil {
509 ce.Write()
510 }
511 })
512 assert.True(t, stub.Exited, "Expected calls to logger.Check(FatalLevel, ...) to terminate process.")
513
514 assert.Equal(t, 0, logs.Len(), "Shouldn't write out logs when fatal-level logging is disabled.")
515 })
516 }
517
518 func TestLoggerDPanic(t *testing.T) {
519 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
520 assert.NotPanics(t, func() { logger.DPanic("") })
521 assert.Equal(
522 t,
523 []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}},
524 logs.AllUntimed(),
525 "Unexpected log output from DPanic in production mode.",
526 )
527 })
528 withLogger(t, DebugLevel, opts(Development()), func(logger *Logger, logs *observer.ObservedLogs) {
529 assert.Panics(t, func() { logger.DPanic("") })
530 assert.Equal(
531 t,
532 []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}},
533 logs.AllUntimed(),
534 "Unexpected log output from DPanic in development mode.",
535 )
536 })
537 }
538
539 func TestLoggerNoOpsDisabledLevels(t *testing.T) {
540 withLogger(t, WarnLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
541 logger.Info("silence!")
542 assert.Equal(
543 t,
544 []observer.LoggedEntry{},
545 logs.AllUntimed(),
546 "Expected logging at a disabled level to produce no output.",
547 )
548 })
549 }
550
551 func TestLoggerNames(t *testing.T) {
552 tests := []struct {
553 names []string
554 expected string
555 }{
556 {nil, ""},
557 {[]string{""}, ""},
558 {[]string{"foo"}, "foo"},
559 {[]string{"foo", ""}, "foo"},
560 {[]string{"foo", "bar"}, "foo.bar"},
561 {[]string{"foo.bar", "baz"}, "foo.bar.baz"},
562
563 {[]string{"foo.", "bar"}, "foo..bar"},
564 {[]string{"foo", ".bar"}, "foo..bar"},
565 {[]string{"foo.", ".bar"}, "foo...bar"},
566 }
567
568 for _, tt := range tests {
569 withLogger(t, DebugLevel, nil, func(log *Logger, logs *observer.ObservedLogs) {
570 for _, n := range tt.names {
571 log = log.Named(n)
572 }
573 log.Info("")
574 require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.")
575 assert.Equal(t, tt.expected, logs.AllUntimed()[0].LoggerName, "Unexpected logger name from entry.")
576 assert.Equal(t, tt.expected, log.Name(), "Unexpected logger name.")
577 })
578 withSugar(t, DebugLevel, nil, func(log *SugaredLogger, logs *observer.ObservedLogs) {
579 for _, n := range tt.names {
580 log = log.Named(n)
581 }
582 log.Infow("")
583 require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.")
584 assert.Equal(t, tt.expected, logs.AllUntimed()[0].LoggerName, "Unexpected logger name from entry.")
585 assert.Equal(t, tt.expected, log.base.Name(), "Unexpected logger name.")
586 })
587 }
588 }
589
590 func TestLoggerWriteFailure(t *testing.T) {
591 errSink := &ztest.Buffer{}
592 logger := New(
593 zapcore.NewCore(
594 zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
595 zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})),
596 DebugLevel,
597 ),
598 ErrorOutput(errSink),
599 )
600
601 logger.Info("foo")
602
603 assert.Regexp(t, `write error: failed`, errSink.Stripped(), "Expected to log the error to the error output.")
604 assert.True(t, errSink.Called(), "Expected logging an internal error to call Sync the error sink.")
605 }
606
607 func TestLoggerSync(t *testing.T) {
608 withLogger(t, DebugLevel, nil, func(logger *Logger, _ *observer.ObservedLogs) {
609 assert.NoError(t, logger.Sync(), "Expected syncing a test logger to succeed.")
610 assert.NoError(t, logger.Sugar().Sync(), "Expected syncing a sugared logger to succeed.")
611 })
612 }
613
614 func TestLoggerSyncFail(t *testing.T) {
615 noSync := &ztest.Buffer{}
616 err := errors.New("fail")
617 noSync.SetError(err)
618 logger := New(zapcore.NewCore(
619 zapcore.NewJSONEncoder(zapcore.EncoderConfig{}),
620 noSync,
621 DebugLevel,
622 ))
623 assert.Equal(t, err, logger.Sync(), "Expected Logger.Sync to propagate errors.")
624 assert.Equal(t, err, logger.Sugar().Sync(), "Expected SugaredLogger.Sync to propagate errors.")
625 }
626
627 func TestLoggerAddCaller(t *testing.T) {
628 tests := []struct {
629 options []Option
630 pat string
631 }{
632 {opts(), `^undefined$`},
633 {opts(WithCaller(false)), `^undefined$`},
634 {opts(AddCaller()), `.+/logger_test.go:[\d]+$`},
635 {opts(AddCaller(), WithCaller(false)), `^undefined$`},
636 {opts(WithCaller(true)), `.+/logger_test.go:[\d]+$`},
637 {opts(WithCaller(true), WithCaller(false)), `^undefined$`},
638 {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/logger_test.go:[\d]+$`},
639 {opts(AddCaller(), AddCallerSkip(1)), `.+/common_test.go:[\d]+$`},
640 {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(3)), `.+/src/runtime/.*:[\d]+$`},
641 }
642 for _, tt := range tests {
643 withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) {
644
645 logger = logger.Sugar().Desugar()
646 logger.Info("")
647 output := logs.AllUntimed()
648 assert.Equal(t, 1, len(output), "Unexpected number of logs written out.")
649 assert.Regexp(
650 t,
651 tt.pat,
652 output[0].Caller,
653 "Expected to find package name and file name in output.",
654 )
655 })
656 }
657 }
658
659 func TestLoggerAddCallerFunction(t *testing.T) {
660 tests := []struct {
661 options []Option
662 loggerFunction string
663 sugaredFunction string
664 }{
665 {
666 options: opts(),
667 loggerFunction: "",
668 sugaredFunction: "",
669 },
670 {
671 options: opts(WithCaller(false)),
672 loggerFunction: "",
673 sugaredFunction: "",
674 },
675 {
676 options: opts(AddCaller()),
677 loggerFunction: "go.uber.org/zap.infoLog",
678 sugaredFunction: "go.uber.org/zap.infoLogSugared",
679 },
680 {
681 options: opts(AddCaller(), WithCaller(false)),
682 loggerFunction: "",
683 sugaredFunction: "",
684 },
685 {
686 options: opts(WithCaller(true)),
687 loggerFunction: "go.uber.org/zap.infoLog",
688 sugaredFunction: "go.uber.org/zap.infoLogSugared",
689 },
690 {
691 options: opts(WithCaller(true), WithCaller(false)),
692 loggerFunction: "",
693 sugaredFunction: "",
694 },
695 {
696 options: opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)),
697 loggerFunction: "go.uber.org/zap.infoLog",
698 sugaredFunction: "go.uber.org/zap.infoLogSugared",
699 },
700 {
701 options: opts(AddCaller(), AddCallerSkip(2)),
702 loggerFunction: "go.uber.org/zap.withLogger",
703 sugaredFunction: "go.uber.org/zap.withLogger",
704 },
705 {
706 options: opts(AddCaller(), AddCallerSkip(2), AddCallerSkip(3)),
707 loggerFunction: "runtime.goexit",
708 sugaredFunction: "runtime.goexit",
709 },
710 }
711 for _, tt := range tests {
712 withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) {
713
714 logger = logger.Sugar().Desugar()
715 infoLog(logger, "")
716 infoLogSugared(logger.Sugar(), "")
717 infoLog(logger.Sugar().Desugar(), "")
718
719 entries := logs.AllUntimed()
720 assert.Equal(t, 3, len(entries), "Unexpected number of logs written out.")
721 for _, entry := range []observer.LoggedEntry{entries[0], entries[2]} {
722 assert.Regexp(
723 t,
724 tt.loggerFunction,
725 entry.Caller.Function,
726 "Expected to find function name in output.",
727 )
728 }
729 assert.Regexp(
730 t,
731 tt.sugaredFunction,
732 entries[1].Caller.Function,
733 "Expected to find function name in output.",
734 )
735 })
736 }
737 }
738
739 func TestLoggerAddCallerFail(t *testing.T) {
740 errBuf := &ztest.Buffer{}
741 withLogger(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) {
742 log.Info("Failure.")
743 assert.Regexp(
744 t,
745 `Logger.check error: failed to get caller`,
746 errBuf.String(),
747 "Didn't find expected failure message.",
748 )
749 assert.Equal(
750 t,
751 logs.AllUntimed()[0].Message,
752 "Failure.",
753 "Expected original message to survive failures in runtime.Caller.")
754 assert.Equal(
755 t,
756 logs.AllUntimed()[0].Caller.Function,
757 "",
758 "Expected function name to be empty string.")
759 })
760 }
761
762 func TestLoggerReplaceCore(t *testing.T) {
763 replace := WrapCore(func(zapcore.Core) zapcore.Core {
764 return zapcore.NewNopCore()
765 })
766 withLogger(t, DebugLevel, opts(replace), func(logger *Logger, logs *observer.ObservedLogs) {
767 logger.Debug("")
768 logger.Info("")
769 logger.Warn("")
770 assert.Equal(t, 0, logs.Len(), "Expected no-op core to write no logs.")
771 })
772 }
773
774 func TestLoggerIncreaseLevel(t *testing.T) {
775 withLogger(t, DebugLevel, opts(IncreaseLevel(WarnLevel)), func(logger *Logger, logs *observer.ObservedLogs) {
776 logger.Info("logger.Info")
777 logger.Warn("logger.Warn")
778 logger.Error("logger.Error")
779 require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.")
780 assert.Equal(
781 t,
782 logs.AllUntimed()[0].Message,
783 "logger.Warn",
784 "Expected first logged message to be warn level message",
785 )
786 })
787 }
788
789 func TestLoggerHooks(t *testing.T) {
790 hook, seen := makeCountingHook()
791 withLogger(t, DebugLevel, opts(Hooks(hook)), func(logger *Logger, logs *observer.ObservedLogs) {
792 logger.Debug("")
793 logger.Info("")
794 })
795 assert.Equal(t, int64(2), seen.Load(), "Hook saw an unexpected number of logs.")
796 }
797
798 func TestLoggerConcurrent(t *testing.T) {
799 withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
800 child := logger.With(String("foo", "bar"))
801
802 wg := &sync.WaitGroup{}
803 runConcurrently(5, 10, wg, func() {
804 logger.Info("", String("foo", "bar"))
805 })
806 runConcurrently(5, 10, wg, func() {
807 child.Info("")
808 })
809
810 wg.Wait()
811
812
813 assert.Equal(t, 100, logs.Len(), "Unexpected number of logs written out.")
814 for _, obs := range logs.AllUntimed() {
815 assert.Equal(
816 t,
817 observer.LoggedEntry{
818 Entry: zapcore.Entry{Level: InfoLevel},
819 Context: []Field{String("foo", "bar")},
820 },
821 obs,
822 "Unexpected log output.",
823 )
824 }
825 })
826 }
827
828 func TestLoggerFatalOnNoop(t *testing.T) {
829 exitStub := exit.Stub()
830 defer exitStub.Unstub()
831 core, _ := observer.New(InfoLevel)
832
833
834 New(core, WithFatalHook(zapcore.WriteThenNoop)).Fatal("great sadness")
835 assert.True(t, exitStub.Exited, "must exit for WriteThenNoop")
836 assert.Equal(t, 1, exitStub.Code, "must exit with status 1 for WriteThenNoop")
837 }
838
839 func TestLoggerCustomOnPanic(t *testing.T) {
840 tests := []struct {
841 msg string
842 level zapcore.Level
843 opts []Option
844 finished bool
845 want []observer.LoggedEntry
846 recoverValue any
847 }{
848 {
849 msg: "panic with nil hook",
850 level: PanicLevel,
851 opts: opts(WithPanicHook(nil)),
852 finished: false,
853 want: []observer.LoggedEntry{
854 {
855 Entry: zapcore.Entry{Level: PanicLevel, Message: "foobar"},
856 Context: []Field{},
857 },
858 },
859 recoverValue: "foobar",
860 },
861 {
862 msg: "panic with noop hook",
863 level: PanicLevel,
864 opts: opts(WithPanicHook(zapcore.WriteThenNoop)),
865 finished: false,
866 want: []observer.LoggedEntry{
867 {
868 Entry: zapcore.Entry{Level: PanicLevel, Message: "foobar"},
869 Context: []Field{},
870 },
871 },
872 recoverValue: "foobar",
873 },
874 {
875 msg: "no panic with goexit hook",
876 level: PanicLevel,
877 opts: opts(WithPanicHook(zapcore.WriteThenGoexit)),
878 finished: false,
879 want: []observer.LoggedEntry{
880 {
881 Entry: zapcore.Entry{Level: PanicLevel, Message: "foobar"},
882 Context: []Field{},
883 },
884 },
885 recoverValue: nil,
886 },
887 {
888 msg: "dpanic no panic in development mode with goexit hook",
889 level: DPanicLevel,
890 opts: opts(WithPanicHook(zapcore.WriteThenGoexit), Development()),
891 finished: false,
892 want: []observer.LoggedEntry{
893 {
894 Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"},
895 Context: []Field{},
896 },
897 },
898 recoverValue: nil,
899 },
900 {
901 msg: "dpanic panic in development mode with noop hook",
902 level: DPanicLevel,
903 opts: opts(WithPanicHook(zapcore.WriteThenNoop), Development()),
904 finished: false,
905 want: []observer.LoggedEntry{
906 {
907 Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"},
908 Context: []Field{},
909 },
910 },
911 recoverValue: "foobar",
912 },
913 {
914 msg: "dpanic no exit in production mode with goexit hook",
915 level: DPanicLevel,
916 opts: opts(WithPanicHook(zapcore.WriteThenPanic)),
917 finished: true,
918 want: []observer.LoggedEntry{
919 {
920 Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"},
921 Context: []Field{},
922 },
923 },
924 recoverValue: nil,
925 },
926 {
927 msg: "dpanic no panic in production mode with panic hook",
928 level: DPanicLevel,
929 opts: opts(WithPanicHook(zapcore.WriteThenPanic)),
930 finished: true,
931 want: []observer.LoggedEntry{
932 {
933 Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"},
934 Context: []Field{},
935 },
936 },
937 recoverValue: nil,
938 },
939 }
940
941 for _, tt := range tests {
942 t.Run(tt.msg, func(t *testing.T) {
943 withLogger(t, InfoLevel, tt.opts, func(logger *Logger, logs *observer.ObservedLogs) {
944 var finished bool
945 recovered := make(chan any)
946 go func() {
947 defer func() {
948 recovered <- recover()
949 }()
950
951 logger.Log(tt.level, "foobar")
952 finished = true
953 }()
954
955 assert.Equal(t, tt.recoverValue, <-recovered, "unexpected value from recover()")
956 assert.Equal(t, tt.finished, finished, "expect goroutine finished state doesn't match")
957 assert.Equal(t, tt.want, logs.AllUntimed(), "unexpected logs")
958 })
959 })
960 }
961 }
962
963 func TestLoggerCustomOnFatal(t *testing.T) {
964 tests := []struct {
965 msg string
966 onFatal zapcore.CheckWriteAction
967 recoverValue interface{}
968 }{
969 {
970 msg: "panic",
971 onFatal: zapcore.WriteThenPanic,
972 recoverValue: "fatal",
973 },
974 {
975 msg: "goexit",
976 onFatal: zapcore.WriteThenGoexit,
977 recoverValue: nil,
978 },
979 }
980
981 for _, tt := range tests {
982 t.Run(tt.msg, func(t *testing.T) {
983 withLogger(t, InfoLevel, opts(OnFatal(tt.onFatal)), func(logger *Logger, logs *observer.ObservedLogs) {
984 var finished bool
985 recovered := make(chan interface{})
986 go func() {
987 defer func() {
988 recovered <- recover()
989 }()
990
991 logger.Fatal("fatal")
992 finished = true
993 }()
994
995 assert.Equal(t, tt.recoverValue, <-recovered, "unexpected value from recover()")
996 assert.False(t, finished, "expect goroutine to not finish after Fatal")
997
998 assert.Equal(t, []observer.LoggedEntry{{
999 Entry: zapcore.Entry{Level: FatalLevel, Message: "fatal"},
1000 Context: []Field{},
1001 }}, logs.AllUntimed(), "unexpected logs")
1002 })
1003 })
1004 }
1005 }
1006
1007 type customWriteHook struct {
1008 called bool
1009 }
1010
1011 func (h *customWriteHook) OnWrite(_ *zapcore.CheckedEntry, _ []Field) {
1012 h.called = true
1013 }
1014
1015 func TestLoggerWithFatalHook(t *testing.T) {
1016 var h customWriteHook
1017 withLogger(t, InfoLevel, opts(WithFatalHook(&h)), func(logger *Logger, logs *observer.ObservedLogs) {
1018 logger.Fatal("great sadness")
1019 assert.True(t, h.called)
1020 assert.Equal(t, 1, logs.FilterLevelExact(FatalLevel).Len())
1021 })
1022 }
1023
1024 func TestNopLogger(t *testing.T) {
1025 logger := NewNop()
1026
1027 t.Run("basic levels", func(t *testing.T) {
1028 logger.Debug("foo", String("k", "v"))
1029 logger.Info("bar", Int("x", 42))
1030 logger.Warn("baz", Strings("ks", []string{"a", "b"}))
1031 logger.Error("qux", Error(errors.New("great sadness")))
1032 })
1033
1034 t.Run("DPanic", func(t *testing.T) {
1035 logger.With(String("component", "whatever")).DPanic("stuff")
1036 })
1037
1038 t.Run("Panic", func(t *testing.T) {
1039 assert.Panics(t, func() {
1040 logger.Panic("great sadness")
1041 }, "Nop logger should still cause panics.")
1042 })
1043 }
1044
1045 func TestMust(t *testing.T) {
1046 t.Run("must without an error does not panic", func(t *testing.T) {
1047 assert.NotPanics(t, func() { Must(NewNop(), nil) }, "must paniced with no error")
1048 })
1049
1050 t.Run("must with an error panics", func(t *testing.T) {
1051 assert.Panics(t, func() { Must(nil, errors.New("an error")) }, "must did not panic with an error")
1052 })
1053 }
1054
1055 func infoLog(logger *Logger, msg string, fields ...Field) {
1056 logger.Info(msg, fields...)
1057 }
1058
1059 func infoLogSugared(logger *SugaredLogger, args ...interface{}) {
1060 logger.Info(args...)
1061 }
1062
View as plain text