logger_bench_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. "runtime"
  24. "strconv"
  25. "sync"
  26. "testing"
  27. "time"
  28. "go.uber.org/zap/internal/ztest"
  29. "go.uber.org/zap/zapcore"
  30. )
  31. type user struct {
  32. Name string
  33. Email string
  34. CreatedAt time.Time
  35. }
  36. func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error {
  37. enc.AddString("name", u.Name)
  38. enc.AddString("email", u.Email)
  39. enc.AddInt64("created_at", u.CreatedAt.UnixNano())
  40. return nil
  41. }
  42. var _jane = &user{
  43. Name: "Jane Doe",
  44. Email: "jane@test.com",
  45. CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC),
  46. }
  47. func withBenchedLogger(b *testing.B, f func(*Logger)) {
  48. logger := New(
  49. zapcore.NewCore(
  50. zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
  51. &ztest.Discarder{},
  52. DebugLevel,
  53. ))
  54. b.ResetTimer()
  55. b.RunParallel(func(pb *testing.PB) {
  56. for pb.Next() {
  57. f(logger)
  58. }
  59. })
  60. }
  61. func BenchmarkNoContext(b *testing.B) {
  62. withBenchedLogger(b, func(log *Logger) {
  63. log.Info("No context.")
  64. })
  65. }
  66. func BenchmarkBoolField(b *testing.B) {
  67. withBenchedLogger(b, func(log *Logger) {
  68. log.Info("Boolean.", Bool("foo", true))
  69. })
  70. }
  71. func BenchmarkByteStringField(b *testing.B) {
  72. val := []byte("bar")
  73. withBenchedLogger(b, func(log *Logger) {
  74. log.Info("ByteString.", ByteString("foo", val))
  75. })
  76. }
  77. func BenchmarkFloat64Field(b *testing.B) {
  78. withBenchedLogger(b, func(log *Logger) {
  79. log.Info("Floating point.", Float64("foo", 3.14))
  80. })
  81. }
  82. func BenchmarkIntField(b *testing.B) {
  83. withBenchedLogger(b, func(log *Logger) {
  84. log.Info("Integer.", Int("foo", 42))
  85. })
  86. }
  87. func BenchmarkInt64Field(b *testing.B) {
  88. withBenchedLogger(b, func(log *Logger) {
  89. log.Info("64-bit integer.", Int64("foo", 42))
  90. })
  91. }
  92. func BenchmarkStringField(b *testing.B) {
  93. withBenchedLogger(b, func(log *Logger) {
  94. log.Info("Strings.", String("foo", "bar"))
  95. })
  96. }
  97. func BenchmarkStringerField(b *testing.B) {
  98. withBenchedLogger(b, func(log *Logger) {
  99. log.Info("Level.", Stringer("foo", InfoLevel))
  100. })
  101. }
  102. func BenchmarkTimeField(b *testing.B) {
  103. t := time.Unix(0, 0)
  104. withBenchedLogger(b, func(log *Logger) {
  105. log.Info("Time.", Time("foo", t))
  106. })
  107. }
  108. func BenchmarkDurationField(b *testing.B) {
  109. withBenchedLogger(b, func(log *Logger) {
  110. log.Info("Duration", Duration("foo", time.Second))
  111. })
  112. }
  113. func BenchmarkErrorField(b *testing.B) {
  114. err := errors.New("egad")
  115. withBenchedLogger(b, func(log *Logger) {
  116. log.Info("Error.", Error(err))
  117. })
  118. }
  119. func BenchmarkErrorsField(b *testing.B) {
  120. errs := []error{
  121. errors.New("egad"),
  122. errors.New("oh no"),
  123. errors.New("dear me"),
  124. errors.New("such fail"),
  125. }
  126. withBenchedLogger(b, func(log *Logger) {
  127. log.Info("Errors.", Errors("errors", errs))
  128. })
  129. }
  130. func BenchmarkStackField(b *testing.B) {
  131. withBenchedLogger(b, func(log *Logger) {
  132. log.Info("Error.", Stack("stacktrace"))
  133. })
  134. }
  135. func BenchmarkObjectField(b *testing.B) {
  136. withBenchedLogger(b, func(log *Logger) {
  137. log.Info("Arbitrary ObjectMarshaler.", Object("user", _jane))
  138. })
  139. }
  140. func BenchmarkReflectField(b *testing.B) {
  141. withBenchedLogger(b, func(log *Logger) {
  142. log.Info("Reflection-based serialization.", Reflect("user", _jane))
  143. })
  144. }
  145. func BenchmarkAddCallerHook(b *testing.B) {
  146. logger := New(
  147. zapcore.NewCore(
  148. zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
  149. &ztest.Discarder{},
  150. InfoLevel,
  151. ),
  152. AddCaller(),
  153. )
  154. b.ResetTimer()
  155. b.RunParallel(func(pb *testing.PB) {
  156. for pb.Next() {
  157. logger.Info("Caller.")
  158. }
  159. })
  160. }
  161. func BenchmarkAddCallerAndStacktrace(b *testing.B) {
  162. logger := New(
  163. zapcore.NewCore(
  164. zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
  165. &ztest.Discarder{},
  166. InfoLevel,
  167. ),
  168. AddCaller(),
  169. AddStacktrace(WarnLevel),
  170. )
  171. b.ResetTimer()
  172. b.RunParallel(func(pb *testing.PB) {
  173. for pb.Next() {
  174. logger.Warn("Caller and stacktrace.")
  175. }
  176. })
  177. }
  178. func Benchmark5WithsUsed(b *testing.B) {
  179. benchmarkWithUsed(b, (*Logger).With, 5, true)
  180. }
  181. // This benchmark will be used in future as a
  182. // baseline for improving
  183. func Benchmark5WithsNotUsed(b *testing.B) {
  184. benchmarkWithUsed(b, (*Logger).With, 5, false)
  185. }
  186. func Benchmark5WithLazysUsed(b *testing.B) {
  187. benchmarkWithUsed(b, (*Logger).WithLazy, 5, true)
  188. }
  189. // This benchmark will be used in future as a
  190. // baseline for improving
  191. func Benchmark5WithLazysNotUsed(b *testing.B) {
  192. benchmarkWithUsed(b, (*Logger).WithLazy, 5, false)
  193. }
  194. func benchmarkWithUsed(b *testing.B, withMethod func(*Logger, ...zapcore.Field) *Logger, N int, use bool) {
  195. keys := make([]string, N)
  196. values := make([]string, N)
  197. for i := 0; i < N; i++ {
  198. keys[i] = "k" + strconv.Itoa(i)
  199. values[i] = "v" + strconv.Itoa(i)
  200. }
  201. b.ResetTimer()
  202. withBenchedLogger(b, func(log *Logger) {
  203. for i := 0; i < N; i++ {
  204. log = withMethod(log, String(keys[i], values[i]))
  205. }
  206. if use {
  207. log.Info("used")
  208. return
  209. }
  210. runtime.KeepAlive(log)
  211. })
  212. }
  213. func Benchmark10Fields(b *testing.B) {
  214. withBenchedLogger(b, func(log *Logger) {
  215. log.Info("Ten fields, passed at the log site.",
  216. Int("one", 1),
  217. Int("two", 2),
  218. Int("three", 3),
  219. Int("four", 4),
  220. Int("five", 5),
  221. Int("six", 6),
  222. Int("seven", 7),
  223. Int("eight", 8),
  224. Int("nine", 9),
  225. Int("ten", 10),
  226. )
  227. })
  228. }
  229. func Benchmark100Fields(b *testing.B) {
  230. const batchSize = 50
  231. logger := New(zapcore.NewCore(
  232. zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig),
  233. &ztest.Discarder{},
  234. DebugLevel,
  235. ))
  236. // Don't include allocating these helper slices in the benchmark. Since
  237. // access to them isn't synchronized, we can't run the benchmark in
  238. // parallel.
  239. first := make([]Field, batchSize)
  240. second := make([]Field, batchSize)
  241. b.ResetTimer()
  242. for i := 0; i < b.N; i++ {
  243. for i := 0; i < batchSize; i++ {
  244. // We're duplicating keys, but that doesn't affect performance.
  245. first[i] = Int("foo", i)
  246. second[i] = Int("foo", i+batchSize)
  247. }
  248. logger.With(first...).Info("Child loggers with lots of context.", second...)
  249. }
  250. }
  251. func BenchmarkAny(b *testing.B) {
  252. key := "some-long-string-longer-than-16"
  253. tests := []struct {
  254. name string
  255. typed func() Field
  256. anyArg any
  257. }{
  258. {
  259. name: "string",
  260. typed: func() Field { return String(key, "yet-another-long-string") },
  261. anyArg: "yet-another-long-string",
  262. },
  263. {
  264. name: "stringer",
  265. typed: func() Field { return Stringer(key, InfoLevel) },
  266. anyArg: InfoLevel,
  267. },
  268. }
  269. for _, tt := range tests {
  270. b.Run(tt.name, func(b *testing.B) {
  271. b.Run("field-only", func(b *testing.B) {
  272. b.Run("typed", func(b *testing.B) {
  273. withBenchedLogger(b, func(log *Logger) {
  274. f := tt.typed()
  275. runtime.KeepAlive(f)
  276. })
  277. })
  278. b.Run("any", func(b *testing.B) {
  279. withBenchedLogger(b, func(log *Logger) {
  280. f := Any(key, tt.anyArg)
  281. runtime.KeepAlive(f)
  282. })
  283. })
  284. })
  285. b.Run("log", func(b *testing.B) {
  286. b.Run("typed", func(b *testing.B) {
  287. withBenchedLogger(b, func(log *Logger) {
  288. log.Info("", tt.typed())
  289. })
  290. })
  291. b.Run("any", func(b *testing.B) {
  292. withBenchedLogger(b, func(log *Logger) {
  293. log.Info("", Any(key, tt.anyArg))
  294. })
  295. })
  296. })
  297. b.Run("log-go", func(b *testing.B) {
  298. b.Run("typed", func(b *testing.B) {
  299. withBenchedLogger(b, func(log *Logger) {
  300. var wg sync.WaitGroup
  301. wg.Add(1)
  302. go func() {
  303. log.Info("", tt.typed())
  304. wg.Done()
  305. }()
  306. wg.Wait()
  307. })
  308. })
  309. b.Run("any", func(b *testing.B) {
  310. withBenchedLogger(b, func(log *Logger) {
  311. var wg sync.WaitGroup
  312. wg.Add(1)
  313. go func() {
  314. log.Info("", Any(key, tt.anyArg))
  315. wg.Done()
  316. }()
  317. wg.Wait()
  318. })
  319. })
  320. })
  321. })
  322. }
  323. }