123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- // Copyright (c) 2016 Uber Technologies, Inc.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- package zapcore
- import (
- "time"
- "go.uber.org/atomic"
- )
- const (
- _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 Level, key string) *counter {
- i := lvl - _minLevel
- j := fnv32a(key) % _countersPerLevel
- return &cs[i][j]
- }
- // fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc
- func fnv32a(s string) uint32 {
- const (
- offset32 = 2166136261
- prime32 = 16777619
- )
- hash := uint32(offset32)
- for i := 0; i < len(s); i++ {
- hash ^= uint32(s[i])
- hash *= prime32
- }
- return hash
- }
- func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 {
- tn := t.UnixNano()
- resetAfter := c.resetAt.Load()
- if resetAfter > tn {
- return c.counter.Inc()
- }
- c.counter.Store(1)
- newResetAfter := tn + tick.Nanoseconds()
- if !c.resetAt.CAS(resetAfter, newResetAfter) {
- // We raced with another goroutine trying to reset, and it also reset
- // the counter to 1, so we need to reincrement the counter.
- return c.counter.Inc()
- }
- return 1
- }
- // SamplingDecision is a decision represented as a bit field made by sampler.
- // More decisions may be added in the future.
- type SamplingDecision uint32
- const (
- // LogDropped indicates that the Sampler dropped a log entry.
- LogDropped SamplingDecision = 1 << iota
- // LogSampled indicates that the Sampler sampled a log entry.
- LogSampled
- )
- // optionFunc wraps a func so it satisfies the SamplerOption interface.
- type optionFunc func(*sampler)
- func (f optionFunc) apply(s *sampler) {
- f(s)
- }
- // SamplerOption configures a Sampler.
- type SamplerOption interface {
- apply(*sampler)
- }
- // nopSamplingHook is the default hook used by sampler.
- func nopSamplingHook(Entry, SamplingDecision) {}
- // SamplerHook registers a function which will be called when Sampler makes a
- // decision.
- //
- // This hook may be used to get visibility into the performance of the sampler.
- // For example, use it to track metrics of dropped versus sampled logs.
- //
- // var dropped atomic.Int64
- // zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) {
- // if dec&zapcore.LogDropped > 0 {
- // dropped.Inc()
- // }
- // })
- func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption {
- return optionFunc(func(s *sampler) {
- s.hook = hook
- })
- }
- // NewSamplerWithOptions creates a Core that samples incoming entries, which
- // caps the CPU and I/O load of logging while attempting to preserve a
- // representative subset of your logs.
- //
- // Zap samples by logging the first N entries with a given level and message
- // each tick. If more Entries with the same level and message are seen during
- // the same interval, every Mth message is logged and the rest are dropped.
- //
- // Sampler can be configured to report sampling decisions with the SamplerHook
- // option.
- //
- // Keep in mind that zap's sampling implementation is optimized for speed over
- // absolute precision; under load, each tick may be slightly over- or
- // under-sampled.
- func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core {
- s := &sampler{
- Core: core,
- tick: tick,
- counts: newCounters(),
- first: uint64(first),
- thereafter: uint64(thereafter),
- hook: nopSamplingHook,
- }
- for _, opt := range opts {
- opt.apply(s)
- }
- return s
- }
- type sampler struct {
- Core
- counts *counters
- tick time.Duration
- first, thereafter uint64
- hook func(Entry, SamplingDecision)
- }
- // NewSampler creates a Core that samples incoming entries, which
- // caps the CPU and I/O load of logging while attempting to preserve a
- // representative subset of your logs.
- //
- // Zap samples by logging the first N entries with a given level and message
- // each tick. If more Entries with the same level and message are seen during
- // the same interval, every Mth message is logged and the rest are dropped.
- //
- // Keep in mind that zap's sampling implementation is optimized for speed over
- // absolute precision; under load, each tick may be slightly over- or
- // under-sampled.
- //
- // Deprecated: use NewSamplerWithOptions.
- func NewSampler(core Core, tick time.Duration, first, thereafter int) Core {
- return NewSamplerWithOptions(core, tick, first, thereafter)
- }
- func (s *sampler) With(fields []Field) Core {
- return &sampler{
- Core: s.Core.With(fields),
- tick: s.tick,
- counts: s.counts,
- first: s.first,
- thereafter: s.thereafter,
- hook: s.hook,
- }
- }
- func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry {
- if !s.Enabled(ent.Level) {
- return ce
- }
- counter := s.counts.get(ent.Level, ent.Message)
- n := counter.IncCheckReset(ent.Time, s.tick)
- if n > s.first && (n-s.first)%s.thereafter != 0 {
- s.hook(ent, LogDropped)
- return ce
- }
- s.hook(ent, LogSampled)
- return s.Core.Check(ent, ce)
- }
|