logger_test.go 25 KB

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