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 22 23 import ( 24 "sync/atomic" 25 "time" 26 ) 27 28 const ( 29 _numLevels = _maxLevel - _minLevel + 1 30 _countersPerLevel = 4096 31 ) 32 33 type counter struct { 34 resetAt atomic.Int64 35 counter atomic.Uint64 36 } 37 38 type counters [_numLevels][_countersPerLevel]counter 39 40 func newCounters() *counters { 41 return &counters{} 42 } 43 44 func (cs *counters) get(lvl Level, key string) *counter { 45 i := lvl - _minLevel 46 j := fnv32a(key) % _countersPerLevel 47 return &cs[i][j] 48 } 49 50 // fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc 51 func fnv32a(s string) uint32 { 52 const ( 53 offset32 = 2166136261 54 prime32 = 16777619 55 ) 56 hash := uint32(offset32) 57 for i := 0; i < len(s); i++ { 58 hash ^= uint32(s[i]) 59 hash *= prime32 60 } 61 return hash 62 } 63 64 func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { 65 tn := t.UnixNano() 66 resetAfter := c.resetAt.Load() 67 if resetAfter > tn { 68 return c.counter.Add(1) 69 } 70 71 c.counter.Store(1) 72 73 newResetAfter := tn + tick.Nanoseconds() 74 if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) { 75 // We raced with another goroutine trying to reset, and it also reset 76 // the counter to 1, so we need to reincrement the counter. 77 return c.counter.Add(1) 78 } 79 80 return 1 81 } 82 83 // SamplingDecision is a decision represented as a bit field made by sampler. 84 // More decisions may be added in the future. 85 type SamplingDecision uint32 86 87 const ( 88 // LogDropped indicates that the Sampler dropped a log entry. 89 LogDropped SamplingDecision = 1 << iota 90 // LogSampled indicates that the Sampler sampled a log entry. 91 LogSampled 92 ) 93 94 // optionFunc wraps a func so it satisfies the SamplerOption interface. 95 type optionFunc func(*sampler) 96 97 func (f optionFunc) apply(s *sampler) { 98 f(s) 99 } 100 101 // SamplerOption configures a Sampler. 102 type SamplerOption interface { 103 apply(*sampler) 104 } 105 106 // nopSamplingHook is the default hook used by sampler. 107 func nopSamplingHook(Entry, SamplingDecision) {} 108 109 // SamplerHook registers a function which will be called when Sampler makes a 110 // decision. 111 // 112 // This hook may be used to get visibility into the performance of the sampler. 113 // For example, use it to track metrics of dropped versus sampled logs. 114 // 115 // var dropped atomic.Int64 116 // zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) { 117 // if dec&zapcore.LogDropped > 0 { 118 // dropped.Inc() 119 // } 120 // }) 121 func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { 122 return optionFunc(func(s *sampler) { 123 s.hook = hook 124 }) 125 } 126 127 // NewSamplerWithOptions creates a Core that samples incoming entries, which 128 // caps the CPU and I/O load of logging while attempting to preserve a 129 // representative subset of your logs. 130 // 131 // Zap samples by logging the first N entries with a given level and message 132 // each tick. If more Entries with the same level and message are seen during 133 // the same interval, every Mth message is logged and the rest are dropped. 134 // 135 // For example, 136 // 137 // core = NewSamplerWithOptions(core, time.Second, 10, 5) 138 // 139 // This will log the first 10 log entries with the same level and message 140 // in a one second interval as-is. Following that, it will allow through 141 // every 5th log entry with the same level and message in that interval. 142 // 143 // If thereafter is zero, the Core will drop all log entries after the first N 144 // in that interval. 145 // 146 // Sampler can be configured to report sampling decisions with the SamplerHook 147 // option. 148 // 149 // Keep in mind that Zap's sampling implementation is optimized for speed over 150 // absolute precision; under load, each tick may be slightly over- or 151 // under-sampled. 152 func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core { 153 s := &sampler{ 154 Core: core, 155 tick: tick, 156 counts: newCounters(), 157 first: uint64(first), 158 thereafter: uint64(thereafter), 159 hook: nopSamplingHook, 160 } 161 for _, opt := range opts { 162 opt.apply(s) 163 } 164 165 return s 166 } 167 168 type sampler struct { 169 Core 170 171 counts *counters 172 tick time.Duration 173 first, thereafter uint64 174 hook func(Entry, SamplingDecision) 175 } 176 177 var ( 178 _ Core = (*sampler)(nil) 179 _ leveledEnabler = (*sampler)(nil) 180 ) 181 182 // NewSampler creates a Core that samples incoming entries, which 183 // caps the CPU and I/O load of logging while attempting to preserve a 184 // representative subset of your logs. 185 // 186 // Zap samples by logging the first N entries with a given level and message 187 // each tick. If more Entries with the same level and message are seen during 188 // the same interval, every Mth message is logged and the rest are dropped. 189 // 190 // Keep in mind that zap's sampling implementation is optimized for speed over 191 // absolute precision; under load, each tick may be slightly over- or 192 // under-sampled. 193 // 194 // Deprecated: use NewSamplerWithOptions. 195 func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { 196 return NewSamplerWithOptions(core, tick, first, thereafter) 197 } 198 199 func (s *sampler) Level() Level { 200 return LevelOf(s.Core) 201 } 202 203 func (s *sampler) With(fields []Field) Core { 204 return &sampler{ 205 Core: s.Core.With(fields), 206 tick: s.tick, 207 counts: s.counts, 208 first: s.first, 209 thereafter: s.thereafter, 210 hook: s.hook, 211 } 212 } 213 214 func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { 215 if !s.Enabled(ent.Level) { 216 return ce 217 } 218 219 if ent.Level >= _minLevel && ent.Level <= _maxLevel { 220 counter := s.counts.get(ent.Level, ent.Message) 221 n := counter.IncCheckReset(ent.Time, s.tick) 222 if n > s.first && (s.thereafter == 0 || (n-s.first)%s.thereafter != 0) { 223 s.hook(ent, LogDropped) 224 return ce 225 } 226 s.hook(ent, LogSampled) 227 } 228 return s.Core.Check(ent, ce) 229 } 230