logger_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // Copyright (c) 2016 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package zap
  21. import (
  22. "errors"
  23. "sync"
  24. "testing"
  25. "go.uber.org/zap/internal/exit"
  26. "go.uber.org/zap/internal/ztest"
  27. "go.uber.org/zap/zapcore"
  28. "go.uber.org/zap/zaptest/observer"
  29. "github.com/stretchr/testify/assert"
  30. "github.com/stretchr/testify/require"
  31. "go.uber.org/atomic"
  32. )
  33. func makeCountingHook() (func(zapcore.Entry) error, *atomic.Int64) {
  34. count := &atomic.Int64{}
  35. h := func(zapcore.Entry) error {
  36. count.Inc()
  37. return nil
  38. }
  39. return h, count
  40. }
  41. func TestLoggerAtomicLevel(t *testing.T) {
  42. // Test that the dynamic level applies to all ancestors and descendants.
  43. dl := NewAtomicLevel()
  44. withLogger(t, dl, nil, func(grandparent *Logger, _ *observer.ObservedLogs) {
  45. parent := grandparent.With(Int("generation", 1))
  46. child := parent.With(Int("generation", 2))
  47. tests := []struct {
  48. setLevel zapcore.Level
  49. testLevel zapcore.Level
  50. enabled bool
  51. }{
  52. {DebugLevel, DebugLevel, true},
  53. {InfoLevel, DebugLevel, false},
  54. {WarnLevel, PanicLevel, true},
  55. }
  56. for _, tt := range tests {
  57. dl.SetLevel(tt.setLevel)
  58. for _, logger := range []*Logger{grandparent, parent, child} {
  59. if tt.enabled {
  60. assert.NotNil(
  61. t,
  62. logger.Check(tt.testLevel, ""),
  63. "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel,
  64. )
  65. } else {
  66. assert.Nil(
  67. t,
  68. logger.Check(tt.testLevel, ""),
  69. "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel,
  70. )
  71. }
  72. }
  73. }
  74. })
  75. }
  76. func TestLoggerInitialFields(t *testing.T) {
  77. fieldOpts := opts(Fields(Int("foo", 42), String("bar", "baz")))
  78. withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) {
  79. logger.Info("")
  80. assert.Equal(
  81. t,
  82. observer.LoggedEntry{Context: []Field{Int("foo", 42), String("bar", "baz")}},
  83. logs.AllUntimed()[0],
  84. "Unexpected output with initial fields set.",
  85. )
  86. })
  87. }
  88. func TestLoggerWith(t *testing.T) {
  89. fieldOpts := opts(Fields(Int("foo", 42)))
  90. withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) {
  91. // Child loggers should have copy-on-write semantics, so two children
  92. // shouldn't stomp on each other's fields or affect the parent's fields.
  93. logger.With(String("one", "two")).Info("")
  94. logger.With(String("three", "four")).Info("")
  95. logger.Info("")
  96. assert.Equal(t, []observer.LoggedEntry{
  97. {Context: []Field{Int("foo", 42), String("one", "two")}},
  98. {Context: []Field{Int("foo", 42), String("three", "four")}},
  99. {Context: []Field{Int("foo", 42)}},
  100. }, logs.AllUntimed(), "Unexpected cross-talk between child loggers.")
  101. })
  102. }
  103. func TestLoggerLogPanic(t *testing.T) {
  104. for _, tt := range []struct {
  105. do func(*Logger)
  106. should bool
  107. expected string
  108. }{
  109. {func(logger *Logger) { logger.Check(PanicLevel, "bar").Write() }, true, "bar"},
  110. {func(logger *Logger) { logger.Panic("baz") }, true, "baz"},
  111. } {
  112. withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  113. if tt.should {
  114. assert.Panics(t, func() { tt.do(logger) }, "Expected panic")
  115. } else {
  116. assert.NotPanics(t, func() { tt.do(logger) }, "Expected no panic")
  117. }
  118. output := logs.AllUntimed()
  119. assert.Equal(t, 1, len(output), "Unexpected number of logs.")
  120. assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.")
  121. assert.Equal(
  122. t,
  123. zapcore.Entry{Message: tt.expected, Level: PanicLevel},
  124. output[0].Entry,
  125. "Unexpected output from panic-level Log.",
  126. )
  127. })
  128. }
  129. }
  130. func TestLoggerLogFatal(t *testing.T) {
  131. for _, tt := range []struct {
  132. do func(*Logger)
  133. expected string
  134. }{
  135. {func(logger *Logger) { logger.Check(FatalLevel, "bar").Write() }, "bar"},
  136. {func(logger *Logger) { logger.Fatal("baz") }, "baz"},
  137. } {
  138. withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  139. stub := exit.WithStub(func() {
  140. tt.do(logger)
  141. })
  142. assert.True(t, stub.Exited, "Expected Fatal logger call to terminate process.")
  143. output := logs.AllUntimed()
  144. assert.Equal(t, 1, len(output), "Unexpected number of logs.")
  145. assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.")
  146. assert.Equal(
  147. t,
  148. zapcore.Entry{Message: tt.expected, Level: FatalLevel},
  149. output[0].Entry,
  150. "Unexpected output from fatal-level Log.",
  151. )
  152. })
  153. }
  154. }
  155. func TestLoggerLeveledMethods(t *testing.T) {
  156. withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  157. tests := []struct {
  158. method func(string, ...Field)
  159. expectedLevel zapcore.Level
  160. }{
  161. {logger.Debug, DebugLevel},
  162. {logger.Info, InfoLevel},
  163. {logger.Warn, WarnLevel},
  164. {logger.Error, ErrorLevel},
  165. {logger.DPanic, DPanicLevel},
  166. }
  167. for i, tt := range tests {
  168. tt.method("")
  169. output := logs.AllUntimed()
  170. assert.Equal(t, i+1, len(output), "Unexpected number of logs.")
  171. assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.")
  172. assert.Equal(
  173. t,
  174. zapcore.Entry{Level: tt.expectedLevel},
  175. output[i].Entry,
  176. "Unexpected output from %s-level logger method.", tt.expectedLevel)
  177. }
  178. })
  179. }
  180. func TestLoggerAlwaysPanics(t *testing.T) {
  181. // Users can disable writing out panic-level logs, but calls to logger.Panic()
  182. // should still call panic().
  183. withLogger(t, FatalLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  184. msg := "Even if output is disabled, logger.Panic should always panic."
  185. assert.Panics(t, func() { logger.Panic("foo") }, msg)
  186. assert.Panics(t, func() {
  187. if ce := logger.Check(PanicLevel, "foo"); ce != nil {
  188. ce.Write()
  189. }
  190. }, msg)
  191. assert.Equal(t, 0, logs.Len(), "Panics shouldn't be written out if PanicLevel is disabled.")
  192. })
  193. }
  194. func TestLoggerAlwaysFatals(t *testing.T) {
  195. // Users can disable writing out fatal-level logs, but calls to logger.Fatal()
  196. // should still terminate the process.
  197. withLogger(t, FatalLevel+1, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  198. stub := exit.WithStub(func() { logger.Fatal("") })
  199. assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.")
  200. stub = exit.WithStub(func() {
  201. if ce := logger.Check(FatalLevel, ""); ce != nil {
  202. ce.Write()
  203. }
  204. })
  205. assert.True(t, stub.Exited, "Expected calls to logger.Check(FatalLevel, ...) to terminate process.")
  206. assert.Equal(t, 0, logs.Len(), "Shouldn't write out logs when fatal-level logging is disabled.")
  207. })
  208. }
  209. func TestLoggerDPanic(t *testing.T) {
  210. withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  211. assert.NotPanics(t, func() { logger.DPanic("") })
  212. assert.Equal(
  213. t,
  214. []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}},
  215. logs.AllUntimed(),
  216. "Unexpected log output from DPanic in production mode.",
  217. )
  218. })
  219. withLogger(t, DebugLevel, opts(Development()), func(logger *Logger, logs *observer.ObservedLogs) {
  220. assert.Panics(t, func() { logger.DPanic("") })
  221. assert.Equal(
  222. t,
  223. []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}},
  224. logs.AllUntimed(),
  225. "Unexpected log output from DPanic in development mode.",
  226. )
  227. })
  228. }
  229. func TestLoggerNoOpsDisabledLevels(t *testing.T) {
  230. withLogger(t, WarnLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  231. logger.Info("silence!")
  232. assert.Equal(
  233. t,
  234. []observer.LoggedEntry{},
  235. logs.AllUntimed(),
  236. "Expected logging at a disabled level to produce no output.",
  237. )
  238. })
  239. }
  240. func TestLoggerNames(t *testing.T) {
  241. tests := []struct {
  242. names []string
  243. expected string
  244. }{
  245. {nil, ""},
  246. {[]string{""}, ""},
  247. {[]string{"foo"}, "foo"},
  248. {[]string{"foo", ""}, "foo"},
  249. {[]string{"foo", "bar"}, "foo.bar"},
  250. {[]string{"foo.bar", "baz"}, "foo.bar.baz"},
  251. // Garbage in, garbage out.
  252. {[]string{"foo.", "bar"}, "foo..bar"},
  253. {[]string{"foo", ".bar"}, "foo..bar"},
  254. {[]string{"foo.", ".bar"}, "foo...bar"},
  255. }
  256. for _, tt := range tests {
  257. withLogger(t, DebugLevel, nil, func(log *Logger, logs *observer.ObservedLogs) {
  258. for _, n := range tt.names {
  259. log = log.Named(n)
  260. }
  261. log.Info("")
  262. require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.")
  263. assert.Equal(t, tt.expected, logs.AllUntimed()[0].Entry.LoggerName, "Unexpected logger name.")
  264. })
  265. withSugar(t, DebugLevel, nil, func(log *SugaredLogger, logs *observer.ObservedLogs) {
  266. for _, n := range tt.names {
  267. log = log.Named(n)
  268. }
  269. log.Infow("")
  270. require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.")
  271. assert.Equal(t, tt.expected, logs.AllUntimed()[0].Entry.LoggerName, "Unexpected logger name.")
  272. })
  273. }
  274. }
  275. func TestLoggerWriteFailure(t *testing.T) {
  276. errSink := &ztest.Buffer{}
  277. logger := New(
  278. zapcore.NewCore(
  279. zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
  280. zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})),
  281. DebugLevel,
  282. ),
  283. ErrorOutput(errSink),
  284. )
  285. logger.Info("foo")
  286. // Should log the error.
  287. assert.Regexp(t, `write error: failed`, errSink.Stripped(), "Expected to log the error to the error output.")
  288. assert.True(t, errSink.Called(), "Expected logging an internal error to call Sync the error sink.")
  289. }
  290. func TestLoggerSync(t *testing.T) {
  291. withLogger(t, DebugLevel, nil, func(logger *Logger, _ *observer.ObservedLogs) {
  292. assert.NoError(t, logger.Sync(), "Expected syncing a test logger to succeed.")
  293. assert.NoError(t, logger.Sugar().Sync(), "Expected syncing a sugared logger to succeed.")
  294. })
  295. }
  296. func TestLoggerSyncFail(t *testing.T) {
  297. noSync := &ztest.Buffer{}
  298. err := errors.New("fail")
  299. noSync.SetError(err)
  300. logger := New(zapcore.NewCore(
  301. zapcore.NewJSONEncoder(zapcore.EncoderConfig{}),
  302. noSync,
  303. DebugLevel,
  304. ))
  305. assert.Equal(t, err, logger.Sync(), "Expected Logger.Sync to propagate errors.")
  306. assert.Equal(t, err, logger.Sugar().Sync(), "Expected SugaredLogger.Sync to propagate errors.")
  307. }
  308. func TestLoggerAddCaller(t *testing.T) {
  309. tests := []struct {
  310. options []Option
  311. pat string
  312. }{
  313. {opts(), `^undefined$`},
  314. {opts(WithCaller(false)), `^undefined$`},
  315. {opts(AddCaller()), `.+/logger_test.go:[\d]+$`},
  316. {opts(AddCaller(), WithCaller(false)), `^undefined$`},
  317. {opts(WithCaller(true)), `.+/logger_test.go:[\d]+$`},
  318. {opts(WithCaller(true), WithCaller(false)), `^undefined$`},
  319. {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/zap/logger_test.go:[\d]+$`},
  320. {opts(AddCaller(), AddCallerSkip(1)), `.+/zap/common_test.go:[\d]+$`},
  321. {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(3)), `.+/src/runtime/.*:[\d]+$`},
  322. }
  323. for _, tt := range tests {
  324. withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) {
  325. // Make sure that sugaring and desugaring resets caller skip properly.
  326. logger = logger.Sugar().Desugar()
  327. logger.Info("")
  328. output := logs.AllUntimed()
  329. assert.Equal(t, 1, len(output), "Unexpected number of logs written out.")
  330. assert.Regexp(
  331. t,
  332. tt.pat,
  333. output[0].Entry.Caller,
  334. "Expected to find package name and file name in output.",
  335. )
  336. })
  337. }
  338. }
  339. func TestLoggerAddCallerFail(t *testing.T) {
  340. errBuf := &ztest.Buffer{}
  341. withLogger(t, DebugLevel, opts(AddCaller(), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) {
  342. log.callerSkip = 1e3
  343. log.Info("Failure.")
  344. assert.Regexp(
  345. t,
  346. `Logger.check error: failed to get caller`,
  347. errBuf.String(),
  348. "Didn't find expected failure message.",
  349. )
  350. assert.Equal(
  351. t,
  352. logs.AllUntimed()[0].Entry.Message,
  353. "Failure.",
  354. "Expected original message to survive failures in runtime.Caller.")
  355. })
  356. }
  357. func TestLoggerReplaceCore(t *testing.T) {
  358. replace := WrapCore(func(zapcore.Core) zapcore.Core {
  359. return zapcore.NewNopCore()
  360. })
  361. withLogger(t, DebugLevel, opts(replace), func(logger *Logger, logs *observer.ObservedLogs) {
  362. logger.Debug("")
  363. logger.Info("")
  364. logger.Warn("")
  365. assert.Equal(t, 0, logs.Len(), "Expected no-op core to write no logs.")
  366. })
  367. }
  368. func TestLoggerIncreaseLevel(t *testing.T) {
  369. withLogger(t, DebugLevel, opts(IncreaseLevel(WarnLevel)), func(logger *Logger, logs *observer.ObservedLogs) {
  370. logger.Info("logger.Info")
  371. logger.Warn("logger.Warn")
  372. logger.Error("logger.Error")
  373. require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.")
  374. assert.Equal(
  375. t,
  376. logs.AllUntimed()[0].Entry.Message,
  377. "logger.Warn",
  378. "Expected first logged message to be warn level message",
  379. )
  380. })
  381. }
  382. func TestLoggerHooks(t *testing.T) {
  383. hook, seen := makeCountingHook()
  384. withLogger(t, DebugLevel, opts(Hooks(hook)), func(logger *Logger, logs *observer.ObservedLogs) {
  385. logger.Debug("")
  386. logger.Info("")
  387. })
  388. assert.Equal(t, int64(2), seen.Load(), "Hook saw an unexpected number of logs.")
  389. }
  390. func TestLoggerConcurrent(t *testing.T) {
  391. withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) {
  392. child := logger.With(String("foo", "bar"))
  393. wg := &sync.WaitGroup{}
  394. runConcurrently(5, 10, wg, func() {
  395. logger.Info("", String("foo", "bar"))
  396. })
  397. runConcurrently(5, 10, wg, func() {
  398. child.Info("")
  399. })
  400. wg.Wait()
  401. // Make sure the output doesn't contain interspersed entries.
  402. assert.Equal(t, 100, logs.Len(), "Unexpected number of logs written out.")
  403. for _, obs := range logs.AllUntimed() {
  404. assert.Equal(
  405. t,
  406. observer.LoggedEntry{
  407. Entry: zapcore.Entry{Level: InfoLevel},
  408. Context: []Field{String("foo", "bar")},
  409. },
  410. obs,
  411. "Unexpected log output.",
  412. )
  413. }
  414. })
  415. }