package zaputil
import (
"sync/atomic"
"time"
"github.com/zeebo/xxh3"
"go.uber.org/zap/zapcore"
)
const (
minLevel = zapcore.DebugLevel
maxLevel = zapcore.FatalLevel
numLevels = maxLevel - minLevel + 1
countersPerLevel = 4096
)
type counter struct {
resetAt atomic.Int64
counter atomic.Uint64
}
type counters [numLevels][countersPerLevel]counter
func newCounters() *counters {
return &counters{}
}
func (cs *counters) get(lvl zapcore.Level, key string) *counter {
i := lvl - minLevel
j := xxh3.HashString(key) % countersPerLevel
return &cs[i][j]
}
func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 {
tn := t.UnixNano()
resetAfter := c.resetAt.Load()
if resetAfter > tn {
return c.counter.Add(1)
}
c.counter.Store(1)
newResetAfter := tn + tick.Nanoseconds()
if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) {
return c.counter.Add(1)
}
return 1
}
type Sampler struct {
counts *counters
tick time.Duration
first, thereafter uint64
}
func NewSampler(tick time.Duration, first, thereafter int) *Sampler {
return &Sampler{
tick: tick,
counts: newCounters(),
first: uint64(first),
thereafter: uint64(thereafter),
}
}
func NewSamplerCore(core zapcore.Core, s *Sampler) zapcore.Core {
return &sampler{
Core: core,
s: s,
}
}
type sampler struct {
zapcore.Core
s *Sampler
}
func (s *sampler) Level() zapcore.Level {
return zapcore.LevelOf(s.Core)
}
func (s *sampler) With(fields []zapcore.Field) zapcore.Core {
return &sampler{
Core: s.Core.With(fields),
s: s.s,
}
}
func (s *sampler) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if !s.Enabled(ent.Level) {
return ce
}
if ent.Level >= minLevel && ent.Level <= maxLevel {
counter := s.s.counts.get(ent.Level, ent.Message)
n := counter.IncCheckReset(ent.Time, s.s.tick)
if n > s.s.first && (s.s.thereafter == 0 || (n-s.s.first)%s.s.thereafter != 0) {
return ce
}
}
return s.Core.Check(ent, ce)
}