@@ -81,33 +81,104 @@ func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 {
8181return 1
8282}
8383
84- type sampler struct {
85- Core
84+ // SamplingDecision is a decision represented as a bit field made by sampler.
85+ // More decisions may be added in the future.
86+ type SamplingDecision uint32
8687
87- counts * counters
88- tick time.Duration
89- first , thereafter uint64
88+ const (
89+ // LogDropped indicates that the Sampler dropped a log entry.
90+ LogDropped SamplingDecision = 1 << iota
91+ // LogSampled indicates that the Sampler sampled a log entry.
92+ LogSampled
93+ )
94+
95+ // optionFunc wraps a func so it satisfies the SamplerOption interface.
96+ type optionFunc func (* sampler )
97+
98+ func (f optionFunc ) apply (s * sampler ) {
99+ f (s )
100+ }
101+
102+ // SamplerOption configures a Sampler.
103+ type SamplerOption interface {
104+ apply (* sampler )
90105}
91106
92- // NewSampler creates a Core that samples incoming entries, which caps the CPU
93- // and I/O load of logging while attempting to preserve a representative subset
94- // of your logs.
107+ // nopSamplingHook is the default hook used by sampler.
108+ func nopSamplingHook (Entry , SamplingDecision ) {}
109+
110+ // SamplerHook registers a function which will be called when Sampler makes a
111+ // decision.
112+ //
113+ // This hook may be used to get visibility into the performance of the sampler.
114+ // For example, use it to track metrics of dropped versus sampled logs.
115+ //
116+ // var dropped atomic.Int64
117+ // zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) {
118+ // if dec&zapcore.LogDropped > 0 {
119+ // dropped.Inc()
120+ // }
121+ // })
122+ func SamplerHook (hook func (entry Entry , dec SamplingDecision )) SamplerOption {
123+ return optionFunc (func (s * sampler ) {
124+ s .hook = hook
125+ })
126+ }
127+
128+ // NewSamplerWithOptions creates a Core that samples incoming entries, which
129+ // caps the CPU and I/O load of logging while attempting to preserve a
130+ // representative subset of your logs.
95131//
96132// Zap samples by logging the first N entries with a given level and message
97133// each tick. If more Entries with the same level and message are seen during
98134// the same interval, every Mth message is logged and the rest are dropped.
99135//
136+ // Sampler can be configured to report sampling decisions with the SamplerHook
137+ // option.
138+ //
100139// Keep in mind that zap's sampling implementation is optimized for speed over
101140// absolute precision; under load, each tick may be slightly over- or
102141// under-sampled.
103- func NewSampler (core Core , tick time.Duration , first , thereafter int ) Core {
104- return & sampler {
142+ func NewSamplerWithOptions (core Core , tick time.Duration , first , thereafter int , opts ... SamplerOption ) Core {
143+ s := & sampler {
105144Core : core ,
106145tick : tick ,
107146counts : newCounters (),
108147first : uint64 (first ),
109148thereafter : uint64 (thereafter ),
149+ hook : nopSamplingHook ,
110150}
151+ for _ , opt := range opts {
152+ opt .apply (s )
153+ }
154+
155+ return s
156+ }
157+
158+ type sampler struct {
159+ Core
160+
161+ counts * counters
162+ tick time.Duration
163+ first , thereafter uint64
164+ hook func (Entry , SamplingDecision )
165+ }
166+
167+ // NewSampler creates a Core that samples incoming entries, which
168+ // caps the CPU and I/O load of logging while attempting to preserve a
169+ // representative subset of your logs.
170+ //
171+ // Zap samples by logging the first N entries with a given level and message
172+ // each tick. If more Entries with the same level and message are seen during
173+ // the same interval, every Mth message is logged and the rest are dropped.
174+ //
175+ // Keep in mind that zap's sampling implementation is optimized for speed over
176+ // absolute precision; under load, each tick may be slightly over- or
177+ // under-sampled.
178+ //
179+ // Deprecated: use NewSamplerWithOptions.
180+ func NewSampler (core Core , tick time.Duration , first , thereafter int ) Core {
181+ return NewSamplerWithOptions (core , tick , first , thereafter )
111182}
112183
113184func (s * sampler ) With (fields []Field ) Core {
@@ -117,6 +188,7 @@ func (s *sampler) With(fields []Field) Core {
117188counts : s .counts ,
118189first : s .first ,
119190thereafter : s .thereafter ,
191+ hook : s .hook ,
120192}
121193}
122194
@@ -128,7 +200,9 @@ func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry {
128200counter := s .counts .get (ent .Level , ent .Message )
129201n := counter .IncCheckReset (ent .Time , s .tick )
130202if n > s .first && (n - s .first )% s .thereafter != 0 {
203+ s .hook (ent , LogDropped )
131204return ce
132205}
206+ s .hook (ent , LogSampled )
133207return s .Core .Check (ent , ce )
134208}
0 commit comments