writer_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // Copyright (c) 2021 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 zapio
  21. import (
  22. "io"
  23. "testing"
  24. "github.com/stretchr/testify/assert"
  25. "github.com/stretchr/testify/require"
  26. "go.uber.org/zap"
  27. "go.uber.org/zap/zapcore"
  28. "go.uber.org/zap/zaptest/observer"
  29. )
  30. func TestWriter(t *testing.T) {
  31. t.Parallel()
  32. tests := []struct {
  33. desc string
  34. level zapcore.Level // defaults to info
  35. writes []string
  36. want []zapcore.Entry
  37. }{
  38. {
  39. desc: "simple",
  40. writes: []string{
  41. "foo\n",
  42. "bar\n",
  43. "baz\n",
  44. },
  45. want: []zapcore.Entry{
  46. {Level: zap.InfoLevel, Message: "foo"},
  47. {Level: zap.InfoLevel, Message: "bar"},
  48. {Level: zap.InfoLevel, Message: "baz"},
  49. },
  50. },
  51. {
  52. desc: "level too low",
  53. level: zap.DebugLevel,
  54. writes: []string{
  55. "foo\n",
  56. "bar\n",
  57. },
  58. want: []zapcore.Entry{},
  59. },
  60. {
  61. desc: "multiple newlines in a message",
  62. level: zap.WarnLevel,
  63. writes: []string{
  64. "foo\nbar\n",
  65. "baz\n",
  66. "qux\nquux\n",
  67. },
  68. want: []zapcore.Entry{
  69. {Level: zap.WarnLevel, Message: "foo"},
  70. {Level: zap.WarnLevel, Message: "bar"},
  71. {Level: zap.WarnLevel, Message: "baz"},
  72. {Level: zap.WarnLevel, Message: "qux"},
  73. {Level: zap.WarnLevel, Message: "quux"},
  74. },
  75. },
  76. {
  77. desc: "message split across multiple writes",
  78. level: zap.ErrorLevel,
  79. writes: []string{
  80. "foo",
  81. "bar\nbaz",
  82. "qux",
  83. },
  84. want: []zapcore.Entry{
  85. {Level: zap.ErrorLevel, Message: "foobar"},
  86. {Level: zap.ErrorLevel, Message: "bazqux"},
  87. },
  88. },
  89. {
  90. desc: "blank lines in the middle",
  91. writes: []string{
  92. "foo\n\nbar\nbaz",
  93. },
  94. want: []zapcore.Entry{
  95. {Level: zap.InfoLevel, Message: "foo"},
  96. {Level: zap.InfoLevel, Message: ""},
  97. {Level: zap.InfoLevel, Message: "bar"},
  98. {Level: zap.InfoLevel, Message: "baz"},
  99. },
  100. },
  101. {
  102. desc: "blank line at the end",
  103. writes: []string{
  104. "foo\nbar\nbaz\n",
  105. },
  106. want: []zapcore.Entry{
  107. {Level: zap.InfoLevel, Message: "foo"},
  108. {Level: zap.InfoLevel, Message: "bar"},
  109. {Level: zap.InfoLevel, Message: "baz"},
  110. },
  111. },
  112. {
  113. desc: "multiple blank line at the end",
  114. writes: []string{
  115. "foo\nbar\nbaz\n\n",
  116. },
  117. want: []zapcore.Entry{
  118. {Level: zap.InfoLevel, Message: "foo"},
  119. {Level: zap.InfoLevel, Message: "bar"},
  120. {Level: zap.InfoLevel, Message: "baz"},
  121. {Level: zap.InfoLevel, Message: ""},
  122. },
  123. },
  124. }
  125. for _, tt := range tests {
  126. tt := tt // for t.Parallel
  127. t.Run(tt.desc, func(t *testing.T) {
  128. t.Parallel()
  129. core, observed := observer.New(zap.InfoLevel)
  130. w := Writer{
  131. Log: zap.New(core),
  132. Level: tt.level,
  133. }
  134. for _, s := range tt.writes {
  135. _, err := io.WriteString(&w, s)
  136. require.NoError(t, err, "Writer.Write failed.")
  137. }
  138. assert.NoError(t, w.Close(), "Writer.Close failed.")
  139. // Turn []observer.LoggedEntry => []zapcore.Entry
  140. got := make([]zapcore.Entry, observed.Len())
  141. for i, ent := range observed.AllUntimed() {
  142. got[i] = ent.Entry
  143. }
  144. assert.Equal(t, tt.want, got, "Logged entries do not match.")
  145. })
  146. }
  147. }
  148. func TestWrite_Sync(t *testing.T) {
  149. t.Parallel()
  150. core, observed := observer.New(zap.InfoLevel)
  151. w := Writer{
  152. Log: zap.New(core),
  153. Level: zap.InfoLevel,
  154. }
  155. io.WriteString(&w, "foo")
  156. io.WriteString(&w, "bar")
  157. t.Run("no sync", func(t *testing.T) {
  158. assert.Zero(t, observed.Len(), "Expected no logs yet")
  159. })
  160. t.Run("sync", func(t *testing.T) {
  161. defer observed.TakeAll()
  162. require.NoError(t, w.Sync(), "Sync must not fail")
  163. assert.Equal(t, []observer.LoggedEntry{
  164. {Entry: zapcore.Entry{Message: "foobar"}, Context: []zapcore.Field{}},
  165. }, observed.AllUntimed(), "Log messages did not match")
  166. })
  167. t.Run("sync on empty", func(t *testing.T) {
  168. require.NoError(t, w.Sync(), "Sync must not fail")
  169. assert.Zero(t, observed.Len(), "Expected no logs yet")
  170. })
  171. }
  172. func BenchmarkWriter(b *testing.B) {
  173. tests := []struct {
  174. name string
  175. writes [][]byte
  176. }{
  177. {
  178. name: "single",
  179. writes: [][]byte{
  180. []byte("foobar\n"),
  181. []byte("bazqux\n"),
  182. },
  183. },
  184. {
  185. name: "splits",
  186. writes: [][]byte{
  187. []byte("foo"),
  188. []byte("bar\nbaz"),
  189. []byte("qux\n"),
  190. },
  191. },
  192. }
  193. writer := Writer{
  194. Log: zap.New(new(partiallyNopCore)),
  195. Level: zapcore.DebugLevel,
  196. }
  197. for _, tt := range tests {
  198. b.Run(tt.name, func(b *testing.B) {
  199. b.ResetTimer()
  200. for i := 0; i < b.N; i++ {
  201. for _, bs := range tt.writes {
  202. writer.Write(bs)
  203. }
  204. }
  205. })
  206. }
  207. }
  208. // partiallyNopCore behaves exactly like NopCore except it always returns true
  209. // for whether the provided level is enabled, and accepts all Check requests.
  210. //
  211. // This lets us measure the overhead of the writer without measuring the cost
  212. // of logging.
  213. type partiallyNopCore struct{}
  214. func (*partiallyNopCore) Enabled(zapcore.Level) bool { return true }
  215. func (c *partiallyNopCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
  216. return ce.AddCore(ent, c)
  217. }
  218. func (c *partiallyNopCore) With([]zapcore.Field) zapcore.Core { return c }
  219. func (*partiallyNopCore) Write(zapcore.Entry, []zapcore.Field) error { return nil }
  220. func (*partiallyNopCore) Sync() error { return nil }