blob: 25f10ca1d750ad6fae91de40e2397c7cc704cbc0 [file] [log] [blame]
khenaidooac637102019-01-14 15:44:34 -05001// Copyright (c) 2016 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
21package zapcore
22
23import (
24 "time"
25
26 "go.uber.org/atomic"
27)
28
29const (
30 _numLevels = _maxLevel - _minLevel + 1
31 _countersPerLevel = 4096
32)
33
34type counter struct {
35 resetAt atomic.Int64
36 counter atomic.Uint64
37}
38
39type counters [_numLevels][_countersPerLevel]counter
40
41func newCounters() *counters {
42 return &counters{}
43}
44
45func (cs *counters) get(lvl Level, key string) *counter {
46 i := lvl - _minLevel
47 j := fnv32a(key) % _countersPerLevel
48 return &cs[i][j]
49}
50
51// fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc
52func fnv32a(s string) uint32 {
53 const (
54 offset32 = 2166136261
55 prime32 = 16777619
56 )
57 hash := uint32(offset32)
58 for i := 0; i < len(s); i++ {
59 hash ^= uint32(s[i])
60 hash *= prime32
61 }
62 return hash
63}
64
65func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 {
66 tn := t.UnixNano()
67 resetAfter := c.resetAt.Load()
68 if resetAfter > tn {
69 return c.counter.Inc()
70 }
71
72 c.counter.Store(1)
73
74 newResetAfter := tn + tick.Nanoseconds()
75 if !c.resetAt.CAS(resetAfter, newResetAfter) {
76 // We raced with another goroutine trying to reset, and it also reset
77 // the counter to 1, so we need to reincrement the counter.
78 return c.counter.Inc()
79 }
80
81 return 1
82}
83
khenaidood948f772021-08-11 17:49:24 -040084// SamplingDecision is a decision represented as a bit field made by sampler.
85// More decisions may be added in the future.
86type SamplingDecision uint32
87
88const (
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.
96type optionFunc func(*sampler)
97
98func (f optionFunc) apply(s *sampler) {
99 f(s)
100}
101
102// SamplerOption configures a Sampler.
103type SamplerOption interface {
104 apply(*sampler)
105}
106
107// nopSamplingHook is the default hook used by sampler.
108func 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// })
122func 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.
131//
132// Zap samples by logging the first N entries with a given level and message
133// each tick. If more Entries with the same level and message are seen during
134// the same interval, every Mth message is logged and the rest are dropped.
135//
136// Sampler can be configured to report sampling decisions with the SamplerHook
137// option.
138//
139// Keep in mind that zap's sampling implementation is optimized for speed over
140// absolute precision; under load, each tick may be slightly over- or
141// under-sampled.
142func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core {
143 s := &sampler{
144 Core: core,
145 tick: tick,
146 counts: newCounters(),
147 first: uint64(first),
148 thereafter: uint64(thereafter),
149 hook: nopSamplingHook,
150 }
151 for _, opt := range opts {
152 opt.apply(s)
153 }
154
155 return s
156}
157
khenaidooac637102019-01-14 15:44:34 -0500158type sampler struct {
159 Core
160
161 counts *counters
162 tick time.Duration
163 first, thereafter uint64
khenaidood948f772021-08-11 17:49:24 -0400164 hook func(Entry, SamplingDecision)
khenaidooac637102019-01-14 15:44:34 -0500165}
166
khenaidood948f772021-08-11 17:49:24 -0400167// 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.
khenaidooac637102019-01-14 15:44:34 -0500170//
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.
khenaidood948f772021-08-11 17:49:24 -0400178//
179// Deprecated: use NewSamplerWithOptions.
khenaidooac637102019-01-14 15:44:34 -0500180func NewSampler(core Core, tick time.Duration, first, thereafter int) Core {
khenaidood948f772021-08-11 17:49:24 -0400181 return NewSamplerWithOptions(core, tick, first, thereafter)
khenaidooac637102019-01-14 15:44:34 -0500182}
183
184func (s *sampler) With(fields []Field) Core {
185 return &sampler{
186 Core: s.Core.With(fields),
187 tick: s.tick,
188 counts: s.counts,
189 first: s.first,
190 thereafter: s.thereafter,
khenaidood948f772021-08-11 17:49:24 -0400191 hook: s.hook,
khenaidooac637102019-01-14 15:44:34 -0500192 }
193}
194
195func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry {
196 if !s.Enabled(ent.Level) {
197 return ce
198 }
199
200 counter := s.counts.get(ent.Level, ent.Message)
201 n := counter.IncCheckReset(ent.Time, s.tick)
202 if n > s.first && (n-s.first)%s.thereafter != 0 {
khenaidood948f772021-08-11 17:49:24 -0400203 s.hook(ent, LogDropped)
khenaidooac637102019-01-14 15:44:34 -0500204 return ce
205 }
khenaidood948f772021-08-11 17:49:24 -0400206 s.hook(ent, LogSampled)
khenaidooac637102019-01-14 15:44:34 -0500207 return s.Core.Check(ent, ce)
208}