sugar_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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. "testing"
  23. "go.uber.org/zap/internal/exit"
  24. "go.uber.org/zap/internal/ztest"
  25. "go.uber.org/zap/zapcore"
  26. "go.uber.org/zap/zaptest/observer"
  27. "github.com/stretchr/testify/assert"
  28. "github.com/stretchr/testify/require"
  29. )
  30. func TestSugarWith(t *testing.T) {
  31. // Convenience functions to create expected error logs.
  32. ignored := func(msg interface{}) observer.LoggedEntry {
  33. return observer.LoggedEntry{
  34. Entry: zapcore.Entry{Level: DPanicLevel, Message: _oddNumberErrMsg},
  35. Context: []Field{Any("ignored", msg)},
  36. }
  37. }
  38. nonString := func(pairs ...invalidPair) observer.LoggedEntry {
  39. return observer.LoggedEntry{
  40. Entry: zapcore.Entry{Level: DPanicLevel, Message: _nonStringKeyErrMsg},
  41. Context: []Field{Array("invalid", invalidPairs(pairs))},
  42. }
  43. }
  44. tests := []struct {
  45. desc string
  46. args []interface{}
  47. expected []Field
  48. errLogs []observer.LoggedEntry
  49. }{
  50. {
  51. desc: "nil args",
  52. args: nil,
  53. expected: []Field{},
  54. errLogs: nil,
  55. },
  56. {
  57. desc: "empty slice of args",
  58. args: []interface{}{},
  59. expected: []Field{},
  60. errLogs: nil,
  61. },
  62. {
  63. desc: "just a dangling key",
  64. args: []interface{}{"should ignore"},
  65. expected: []Field{},
  66. errLogs: []observer.LoggedEntry{ignored("should ignore")},
  67. },
  68. {
  69. desc: "well-formed key-value pairs",
  70. args: []interface{}{"foo", 42, "true", "bar"},
  71. expected: []Field{Int("foo", 42), String("true", "bar")},
  72. errLogs: nil,
  73. },
  74. {
  75. desc: "just a structured field",
  76. args: []interface{}{Int("foo", 42)},
  77. expected: []Field{Int("foo", 42)},
  78. errLogs: nil,
  79. },
  80. {
  81. desc: "structured field and a dangling key",
  82. args: []interface{}{Int("foo", 42), "dangling"},
  83. expected: []Field{Int("foo", 42)},
  84. errLogs: []observer.LoggedEntry{ignored("dangling")},
  85. },
  86. {
  87. desc: "structured field and a dangling non-string key",
  88. args: []interface{}{Int("foo", 42), 13},
  89. expected: []Field{Int("foo", 42)},
  90. errLogs: []observer.LoggedEntry{ignored(13)},
  91. },
  92. {
  93. desc: "key-value pair and a dangling key",
  94. args: []interface{}{"foo", 42, "dangling"},
  95. expected: []Field{Int("foo", 42)},
  96. errLogs: []observer.LoggedEntry{ignored("dangling")},
  97. },
  98. {
  99. desc: "pairs, a structured field, and a dangling key",
  100. args: []interface{}{"first", "field", Int("foo", 42), "baz", "quux", "dangling"},
  101. expected: []Field{String("first", "field"), Int("foo", 42), String("baz", "quux")},
  102. errLogs: []observer.LoggedEntry{ignored("dangling")},
  103. },
  104. {
  105. desc: "one non-string key",
  106. args: []interface{}{"foo", 42, true, "bar"},
  107. expected: []Field{Int("foo", 42)},
  108. errLogs: []observer.LoggedEntry{nonString(invalidPair{2, true, "bar"})},
  109. },
  110. {
  111. desc: "pairs, structured fields, non-string keys, and a dangling key",
  112. args: []interface{}{"foo", 42, true, "bar", Int("structure", 11), 42, "reversed", "baz", "quux", "dangling"},
  113. expected: []Field{Int("foo", 42), Int("structure", 11), String("baz", "quux")},
  114. errLogs: []observer.LoggedEntry{
  115. ignored("dangling"),
  116. nonString(invalidPair{2, true, "bar"}, invalidPair{5, 42, "reversed"}),
  117. },
  118. },
  119. }
  120. for _, tt := range tests {
  121. withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
  122. logger.With(tt.args...).Info("")
  123. output := logs.AllUntimed()
  124. if len(tt.errLogs) > 0 {
  125. for i := range tt.errLogs {
  126. assert.Equal(t, tt.errLogs[i], output[i], "Unexpected error log at position %d for scenario %s.", i, tt.desc)
  127. }
  128. }
  129. assert.Equal(t, len(tt.errLogs)+1, len(output), "Expected only one non-error message to be logged in scenario %s.", tt.desc)
  130. assert.Equal(t, tt.expected, output[len(tt.errLogs)].Context, "Unexpected message context in scenario %s.", tt.desc)
  131. })
  132. }
  133. }
  134. func TestSugarFieldsInvalidPairs(t *testing.T) {
  135. withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
  136. logger.With(42, "foo", []string{"bar"}, "baz").Info("")
  137. output := logs.AllUntimed()
  138. // Double-check that the actual message was logged.
  139. require.Equal(t, 2, len(output), "Unexpected number of entries logged.")
  140. require.Equal(t, observer.LoggedEntry{Context: []Field{}}, output[1], "Unexpected non-error log entry.")
  141. // Assert that the error message's structured fields serialize properly.
  142. require.Equal(t, 1, len(output[0].Context), "Expected one field in error entry context.")
  143. enc := zapcore.NewMapObjectEncoder()
  144. output[0].Context[0].AddTo(enc)
  145. assert.Equal(t, []interface{}{
  146. map[string]interface{}{"position": int64(0), "key": int64(42), "value": "foo"},
  147. map[string]interface{}{"position": int64(2), "key": []interface{}{"bar"}, "value": "baz"},
  148. }, enc.Fields["invalid"], "Unexpected output when logging invalid key-value pairs.")
  149. })
  150. }
  151. type stringerF func() string
  152. func (f stringerF) String() string { return f() }
  153. func TestSugarStructuredLogging(t *testing.T) {
  154. tests := []struct {
  155. msg string
  156. expectMsg string
  157. }{
  158. {"foo", "foo"},
  159. {"", ""},
  160. }
  161. // Common to all test cases.
  162. context := []interface{}{"foo", "bar"}
  163. extra := []interface{}{"baz", false}
  164. expectedFields := []Field{String("foo", "bar"), Bool("baz", false)}
  165. for _, tt := range tests {
  166. withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
  167. logger.With(context...).Debugw(tt.msg, extra...)
  168. logger.With(context...).Infow(tt.msg, extra...)
  169. logger.With(context...).Warnw(tt.msg, extra...)
  170. logger.With(context...).Errorw(tt.msg, extra...)
  171. logger.With(context...).DPanicw(tt.msg, extra...)
  172. expected := make([]observer.LoggedEntry, 5)
  173. for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} {
  174. expected[i] = observer.LoggedEntry{
  175. Entry: zapcore.Entry{Message: tt.expectMsg, Level: lvl},
  176. Context: expectedFields,
  177. }
  178. }
  179. assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
  180. })
  181. }
  182. }
  183. func TestSugarConcatenatingLogging(t *testing.T) {
  184. tests := []struct {
  185. args []interface{}
  186. expect string
  187. }{
  188. {[]interface{}{nil}, "<nil>"},
  189. }
  190. // Common to all test cases.
  191. context := []interface{}{"foo", "bar"}
  192. expectedFields := []Field{String("foo", "bar")}
  193. for _, tt := range tests {
  194. withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
  195. logger.With(context...).Debug(tt.args...)
  196. logger.With(context...).Info(tt.args...)
  197. logger.With(context...).Warn(tt.args...)
  198. logger.With(context...).Error(tt.args...)
  199. logger.With(context...).DPanic(tt.args...)
  200. expected := make([]observer.LoggedEntry, 5)
  201. for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} {
  202. expected[i] = observer.LoggedEntry{
  203. Entry: zapcore.Entry{Message: tt.expect, Level: lvl},
  204. Context: expectedFields,
  205. }
  206. }
  207. assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
  208. })
  209. }
  210. }
  211. func TestSugarTemplatedLogging(t *testing.T) {
  212. tests := []struct {
  213. format string
  214. args []interface{}
  215. expect string
  216. }{
  217. {"", nil, ""},
  218. {"foo", nil, "foo"},
  219. // If the user fails to pass a template, degrade to fmt.Sprint.
  220. {"", []interface{}{"foo"}, "foo"},
  221. }
  222. // Common to all test cases.
  223. context := []interface{}{"foo", "bar"}
  224. expectedFields := []Field{String("foo", "bar")}
  225. for _, tt := range tests {
  226. withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
  227. logger.With(context...).Debugf(tt.format, tt.args...)
  228. logger.With(context...).Infof(tt.format, tt.args...)
  229. logger.With(context...).Warnf(tt.format, tt.args...)
  230. logger.With(context...).Errorf(tt.format, tt.args...)
  231. logger.With(context...).DPanicf(tt.format, tt.args...)
  232. expected := make([]observer.LoggedEntry, 5)
  233. for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} {
  234. expected[i] = observer.LoggedEntry{
  235. Entry: zapcore.Entry{Message: tt.expect, Level: lvl},
  236. Context: expectedFields,
  237. }
  238. }
  239. assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.")
  240. })
  241. }
  242. }
  243. func TestSugarPanicLogging(t *testing.T) {
  244. tests := []struct {
  245. loggerLevel zapcore.Level
  246. f func(*SugaredLogger)
  247. expectedMsg string
  248. }{
  249. {FatalLevel, func(s *SugaredLogger) { s.Panic("foo") }, ""},
  250. {PanicLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"},
  251. {DebugLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"},
  252. {FatalLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, ""},
  253. {PanicLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"},
  254. {DebugLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"},
  255. {FatalLevel, func(s *SugaredLogger) { s.Panicw("foo") }, ""},
  256. {PanicLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"},
  257. {DebugLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"},
  258. }
  259. for _, tt := range tests {
  260. withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) {
  261. assert.Panics(t, func() { tt.f(sugar) }, "Expected panic-level logger calls to panic.")
  262. if tt.expectedMsg != "" {
  263. assert.Equal(t, []observer.LoggedEntry{{
  264. Context: []Field{},
  265. Entry: zapcore.Entry{Message: tt.expectedMsg, Level: PanicLevel},
  266. }}, logs.AllUntimed(), "Unexpected log output.")
  267. } else {
  268. assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.")
  269. }
  270. })
  271. }
  272. }
  273. func TestSugarFatalLogging(t *testing.T) {
  274. tests := []struct {
  275. loggerLevel zapcore.Level
  276. f func(*SugaredLogger)
  277. expectedMsg string
  278. }{
  279. {FatalLevel + 1, func(s *SugaredLogger) { s.Fatal("foo") }, ""},
  280. {FatalLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"},
  281. {DebugLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"},
  282. {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, ""},
  283. {FatalLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"},
  284. {DebugLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"},
  285. {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalw("foo") }, ""},
  286. {FatalLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"},
  287. {DebugLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"},
  288. }
  289. for _, tt := range tests {
  290. withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) {
  291. stub := exit.WithStub(func() { tt.f(sugar) })
  292. assert.True(t, stub.Exited, "Expected all calls to fatal logger methods to exit process.")
  293. if tt.expectedMsg != "" {
  294. assert.Equal(t, []observer.LoggedEntry{{
  295. Context: []Field{},
  296. Entry: zapcore.Entry{Message: tt.expectedMsg, Level: FatalLevel},
  297. }}, logs.AllUntimed(), "Unexpected log output.")
  298. } else {
  299. assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.")
  300. }
  301. })
  302. }
  303. }
  304. func TestSugarAddCaller(t *testing.T) {
  305. tests := []struct {
  306. options []Option
  307. pat string
  308. }{
  309. {opts(AddCaller()), `.+/sugar_test.go:[\d]+$`},
  310. {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/zap/sugar_test.go:[\d]+$`},
  311. {opts(AddCaller(), AddCallerSkip(1)), `.+/zap/common_test.go:[\d]+$`},
  312. {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(5)), `.+/src/runtime/.*:[\d]+$`},
  313. }
  314. for _, tt := range tests {
  315. withSugar(t, DebugLevel, tt.options, func(logger *SugaredLogger, logs *observer.ObservedLogs) {
  316. logger.Info("")
  317. output := logs.AllUntimed()
  318. assert.Equal(t, 1, len(output), "Unexpected number of logs written out.")
  319. assert.Regexp(
  320. t,
  321. tt.pat,
  322. output[0].Entry.Caller,
  323. "Expected to find package name and file name in output.",
  324. )
  325. })
  326. }
  327. }
  328. func TestSugarAddCallerFail(t *testing.T) {
  329. errBuf := &ztest.Buffer{}
  330. withSugar(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *SugaredLogger, logs *observer.ObservedLogs) {
  331. log.Info("Failure.")
  332. assert.Regexp(
  333. t,
  334. `Logger.check error: failed to get caller`,
  335. errBuf.String(),
  336. "Didn't find expected failure message.",
  337. )
  338. assert.Equal(
  339. t,
  340. logs.AllUntimed()[0].Entry.Message,
  341. "Failure.",
  342. "Expected original message to survive failures in runtime.Caller.")
  343. })
  344. }