github.com/Laisky/zap@v1.27.0/zapcore/sampler_test.go (about) 1 // Copyright (c) 2016-2022 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 21 package zapcore_test 22 23 import ( 24 "fmt" 25 "runtime" 26 "sync" 27 "sync/atomic" 28 "testing" 29 "time" 30 31 "github.com/Laisky/zap/internal/ztest" 32 //revive:disable:dot-imports 33 . "github.com/Laisky/zap/zapcore" 34 "github.com/Laisky/zap/zaptest/observer" 35 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 ) 39 40 func fakeSampler(lvl LevelEnabler, tick time.Duration, first, thereafter int) (Core, *observer.ObservedLogs) { 41 core, logs := observer.New(lvl) 42 // Keep using deprecated constructor for cc. 43 core = NewSampler(core, tick, first, thereafter) 44 return core, logs 45 } 46 47 func assertSequence(t testing.TB, logs []observer.LoggedEntry, lvl Level, seq ...int64) { 48 seen := make([]int64, len(logs)) 49 for i, entry := range logs { 50 require.Equal(t, "", entry.Message, "Message wasn't created by writeSequence.") 51 require.Equal(t, 1, len(entry.Context), "Unexpected number of fields.") 52 require.Equal(t, lvl, entry.Level, "Unexpected level.") 53 f := entry.Context[0] 54 require.Equal(t, "iter", f.Key, "Unexpected field key.") 55 require.Equal(t, Int64Type, f.Type, "Unexpected field type") 56 seen[i] = f.Integer 57 } 58 assert.Equal(t, seq, seen, "Unexpected sequence logged at level %v.", lvl) 59 } 60 61 func writeSequence(core Core, n int, lvl Level) { 62 // All tests using writeSequence verify that counters are shared between 63 // parent and child cores. 64 core = core.With([]Field{makeInt64Field("iter", n)}) 65 if ce := core.Check(Entry{Level: lvl, Time: time.Now()}, nil); ce != nil { 66 ce.Write() 67 } 68 } 69 70 func TestSampler(t *testing.T) { 71 for _, lvl := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} { 72 sampler, logs := fakeSampler(DebugLevel, time.Minute, 2, 3) 73 74 // Ensure that counts aren't shared between levels. 75 probeLevel := DebugLevel 76 if lvl == DebugLevel { 77 probeLevel = InfoLevel 78 } 79 for i := 0; i < 10; i++ { 80 writeSequence(sampler, 1, probeLevel) 81 } 82 // Clear any output. 83 logs.TakeAll() 84 85 for i := 1; i < 10; i++ { 86 writeSequence(sampler, i, lvl) 87 } 88 assertSequence(t, logs.TakeAll(), lvl, 1, 2, 5, 8) 89 } 90 } 91 92 func TestLevelOfSampler(t *testing.T) { 93 levels := []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} 94 for _, lvl := range levels { 95 lvl := lvl 96 t.Run(lvl.String(), func(t *testing.T) { 97 t.Parallel() 98 99 sampler, _ := fakeSampler(lvl, time.Minute, 2, 3) 100 assert.Equal(t, lvl, LevelOf(sampler), "Sampler level did not match.") 101 }) 102 } 103 } 104 105 func TestSamplerDisabledLevels(t *testing.T) { 106 sampler, logs := fakeSampler(InfoLevel, time.Minute, 1, 100) 107 108 // Shouldn't be counted, because debug logging isn't enabled. 109 writeSequence(sampler, 1, DebugLevel) 110 writeSequence(sampler, 2, InfoLevel) 111 assertSequence(t, logs.TakeAll(), InfoLevel, 2) 112 } 113 114 func TestSamplerTicking(t *testing.T) { 115 // Ensure that we're resetting the sampler's counter every tick. 116 sampler, logs := fakeSampler(DebugLevel, 10*time.Millisecond, 5, 10) 117 118 // If we log five or fewer messages every tick, none of them should be 119 // dropped. 120 for tick := 0; tick < 2; tick++ { 121 for i := 1; i <= 5; i++ { 122 writeSequence(sampler, i, InfoLevel) 123 } 124 ztest.Sleep(15 * time.Millisecond) 125 } 126 assertSequence( 127 t, 128 logs.TakeAll(), 129 InfoLevel, 130 1, 2, 3, 4, 5, // first tick 131 1, 2, 3, 4, 5, // second tick 132 ) 133 134 // If we log quickly, we should drop some logs. The first five statements 135 // each tick should be logged, then every tenth. 136 for tick := 0; tick < 3; tick++ { 137 for i := 1; i < 18; i++ { 138 writeSequence(sampler, i, InfoLevel) 139 } 140 ztest.Sleep(10 * time.Millisecond) 141 } 142 143 assertSequence( 144 t, 145 logs.TakeAll(), 146 InfoLevel, 147 1, 2, 3, 4, 5, 15, // first tick 148 1, 2, 3, 4, 5, 15, // second tick 149 1, 2, 3, 4, 5, 15, // third tick 150 ) 151 } 152 153 type countingCore struct { 154 logs atomic.Uint32 155 } 156 157 func (c *countingCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { 158 return ce.AddCore(ent, c) 159 } 160 161 func (c *countingCore) Write(Entry, []Field) error { 162 c.logs.Add(1) 163 return nil 164 } 165 166 func (c *countingCore) With([]Field) Core { return c } 167 func (*countingCore) Fields() []Field { return nil } 168 func (*countingCore) Enabled(Level) bool { return true } 169 func (*countingCore) Sync() error { return nil } 170 171 func TestSamplerConcurrent(t *testing.T) { 172 const ( 173 logsPerTick = 10 174 numMessages = 5 175 numTicks = 25 176 numGoroutines = 10 177 tick = 10 * time.Millisecond 178 179 // We'll make a total of, 180 // (numGoroutines * numTicks * logsPerTick * 2) log attempts 181 // with numMessages unique messages. 182 numLogAttempts = numGoroutines * logsPerTick * numTicks * 2 183 // Of those, we'll accept (logsPerTick * numTicks) entries 184 // for each unique message. 185 expectedCount = numMessages * logsPerTick * numTicks 186 // The rest will be dropped. 187 expectedDropped = numLogAttempts - expectedCount 188 ) 189 190 clock := ztest.NewMockClock() 191 192 cc := &countingCore{} 193 194 hook, dropped, sampled := makeSamplerCountingHook() 195 sampler := NewSamplerWithOptions(cc, tick, logsPerTick, 100000, SamplerHook(hook)) 196 197 stop := make(chan struct{}) 198 var wg sync.WaitGroup 199 for i := 0; i < numGoroutines; i++ { 200 wg.Add(1) 201 go func(i int, ticker *time.Ticker) { 202 defer wg.Done() 203 defer ticker.Stop() 204 205 for { 206 select { 207 case <-stop: 208 return 209 210 case <-ticker.C: 211 for j := 0; j < logsPerTick*2; j++ { 212 msg := fmt.Sprintf("msg%v", i%numMessages) 213 ent := Entry{ 214 Level: DebugLevel, 215 Message: msg, 216 Time: clock.Now(), 217 } 218 if ce := sampler.Check(ent, nil); ce != nil { 219 ce.Write() 220 } 221 222 // Give a chance for other goroutines to run. 223 runtime.Gosched() 224 } 225 } 226 } 227 }(i, clock.NewTicker(tick)) 228 } 229 230 clock.Add(tick * numTicks) 231 close(stop) 232 wg.Wait() 233 234 assert.Equal( 235 t, 236 expectedCount, 237 int(cc.logs.Load()), 238 "Unexpected number of logs", 239 ) 240 assert.Equal(t, 241 expectedCount, 242 int(sampled.Load()), 243 "Unexpected number of logs sampled", 244 ) 245 assert.Equal(t, 246 expectedDropped, 247 int(dropped.Load()), 248 "Unexpected number of logs dropped", 249 ) 250 } 251 252 func TestSamplerRaces(t *testing.T) { 253 sampler, _ := fakeSampler(DebugLevel, time.Minute, 1, 1000) 254 255 var wg sync.WaitGroup 256 start := make(chan struct{}) 257 258 for i := 0; i < 100; i++ { 259 wg.Add(1) 260 go func() { 261 <-start 262 for j := 0; j < 100; j++ { 263 writeSequence(sampler, j, InfoLevel) 264 } 265 wg.Done() 266 }() 267 } 268 269 close(start) 270 wg.Wait() 271 } 272 273 func TestSamplerUnknownLevels(t *testing.T) { 274 // Prove that out-of-bounds levels don't panic. 275 unknownLevels := []Level{ 276 DebugLevel - 1, 277 FatalLevel + 1, 278 } 279 280 for _, lvl := range unknownLevels { 281 t.Run(lvl.String(), func(t *testing.T) { 282 sampler, logs := fakeSampler(lvl, time.Minute, 2, 3) 283 for i := 1; i < 10; i++ { 284 writeSequence(sampler, i, lvl) 285 } 286 287 // Expect no sampling for unknown levels. 288 assertSequence(t, logs.TakeAll(), lvl, 1, 2, 3, 4, 5, 6, 7, 8, 9) 289 }) 290 } 291 } 292 293 func TestSamplerWithZeroThereafter(t *testing.T) { 294 var counter countingCore 295 296 // Logs two messages per second. 297 sampler := NewSamplerWithOptions(&counter, time.Second, 2, 0) 298 299 now := time.Now() 300 301 for i := 0; i < 1000; i++ { 302 ent := Entry{ 303 Level: InfoLevel, 304 Message: "msg", 305 Time: now, 306 } 307 if ce := sampler.Check(ent, nil); ce != nil { 308 ce.Write() 309 } 310 } 311 312 assert.Equal(t, 2, int(counter.logs.Load()), 313 "Unexpected number of logs") 314 315 now = now.Add(time.Second) 316 317 for i := 0; i < 1000; i++ { 318 ent := Entry{ 319 Level: InfoLevel, 320 Message: "msg", 321 Time: now, 322 } 323 if ce := sampler.Check(ent, nil); ce != nil { 324 ce.Write() 325 } 326 } 327 328 assert.Equal(t, 4, int(counter.logs.Load()), 329 "Unexpected number of logs") 330 }