github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/logger/zaputil/sampler.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 zaputil 22 23 import ( 24 "sync/atomic" 25 "time" 26 27 "github.com/zeebo/xxh3" 28 "go.uber.org/zap/zapcore" 29 ) 30 31 // this is lifted almost entirely from zap but with one significant change - 32 // the state is injected into the sampler core to allow rebuilding the logger 33 // without resetting the counts. 34 35 const ( 36 minLevel = zapcore.DebugLevel 37 maxLevel = zapcore.FatalLevel 38 39 numLevels = maxLevel - minLevel + 1 40 countersPerLevel = 4096 41 ) 42 43 type counter struct { 44 resetAt atomic.Int64 45 counter atomic.Uint64 46 } 47 48 type counters [numLevels][countersPerLevel]counter 49 50 func newCounters() *counters { 51 return &counters{} 52 } 53 54 func (cs *counters) get(lvl zapcore.Level, key string) *counter { 55 i := lvl - minLevel 56 j := xxh3.HashString(key) % countersPerLevel 57 return &cs[i][j] 58 } 59 60 func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { 61 tn := t.UnixNano() 62 resetAfter := c.resetAt.Load() 63 if resetAfter > tn { 64 return c.counter.Add(1) 65 } 66 67 c.counter.Store(1) 68 69 newResetAfter := tn + tick.Nanoseconds() 70 if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) { 71 // We raced with another goroutine trying to reset, and it also reset 72 // the counter to 1, so we need to reincrement the counter. 73 return c.counter.Add(1) 74 } 75 76 return 1 77 } 78 79 type Sampler struct { 80 counts *counters 81 tick time.Duration 82 first, thereafter uint64 83 } 84 85 func NewSampler(tick time.Duration, first, thereafter int) *Sampler { 86 return &Sampler{ 87 tick: tick, 88 counts: newCounters(), 89 first: uint64(first), 90 thereafter: uint64(thereafter), 91 } 92 } 93 94 func NewSamplerCore(core zapcore.Core, s *Sampler) zapcore.Core { 95 return &sampler{ 96 Core: core, 97 s: s, 98 } 99 } 100 101 type sampler struct { 102 zapcore.Core 103 s *Sampler 104 } 105 106 func (s *sampler) Level() zapcore.Level { 107 return zapcore.LevelOf(s.Core) 108 } 109 110 func (s *sampler) With(fields []zapcore.Field) zapcore.Core { 111 return &sampler{ 112 Core: s.Core.With(fields), 113 s: s.s, 114 } 115 } 116 117 func (s *sampler) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { 118 if !s.Enabled(ent.Level) { 119 return ce 120 } 121 122 if ent.Level >= minLevel && ent.Level <= maxLevel { 123 counter := s.s.counts.get(ent.Level, ent.Message) 124 n := counter.IncCheckReset(ent.Time, s.s.tick) 125 if n > s.s.first && (s.s.thereafter == 0 || (n-s.s.first)%s.s.thereafter != 0) { 126 return ce 127 } 128 } 129 return s.Core.Check(ent, ce) 130 }