github.com/Laisky/zap@v1.27.0/zapcore/sampler_test.go (about)

     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_test
    22  
    23  import (
    24  	"fmt"
    25  	"runtime"
    26  	"sync"
    27  	"sync/atomic"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/Laisky/zap/internal/ztest"
    32  	//revive:disable:dot-imports
    33  	. "github.com/Laisky/zap/zapcore"
    34  	"github.com/Laisky/zap/zaptest/observer"
    35  
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  )
    39  
    40  func fakeSampler(lvl LevelEnabler, tick time.Duration, first, thereafter int) (Core, *observer.ObservedLogs) {
    41  	core, logs := observer.New(lvl)
    42  	// Keep using deprecated constructor for cc.
    43  	core = NewSampler(core, tick, first, thereafter)
    44  	return core, logs
    45  }
    46  
    47  func assertSequence(t testing.TB, logs []observer.LoggedEntry, lvl Level, seq ...int64) {
    48  	seen := make([]int64, len(logs))
    49  	for i, entry := range logs {
    50  		require.Equal(t, "", entry.Message, "Message wasn't created by writeSequence.")
    51  		require.Equal(t, 1, len(entry.Context), "Unexpected number of fields.")
    52  		require.Equal(t, lvl, entry.Level, "Unexpected level.")
    53  		f := entry.Context[0]
    54  		require.Equal(t, "iter", f.Key, "Unexpected field key.")
    55  		require.Equal(t, Int64Type, f.Type, "Unexpected field type")
    56  		seen[i] = f.Integer
    57  	}
    58  	assert.Equal(t, seq, seen, "Unexpected sequence logged at level %v.", lvl)
    59  }
    60  
    61  func writeSequence(core Core, n int, lvl Level) {
    62  	// All tests using writeSequence verify that counters are shared between
    63  	// parent and child cores.
    64  	core = core.With([]Field{makeInt64Field("iter", n)})
    65  	if ce := core.Check(Entry{Level: lvl, Time: time.Now()}, nil); ce != nil {
    66  		ce.Write()
    67  	}
    68  }
    69  
    70  func TestSampler(t *testing.T) {
    71  	for _, lvl := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} {
    72  		sampler, logs := fakeSampler(DebugLevel, time.Minute, 2, 3)
    73  
    74  		// Ensure that counts aren't shared between levels.
    75  		probeLevel := DebugLevel
    76  		if lvl == DebugLevel {
    77  			probeLevel = InfoLevel
    78  		}
    79  		for i := 0; i < 10; i++ {
    80  			writeSequence(sampler, 1, probeLevel)
    81  		}
    82  		// Clear any output.
    83  		logs.TakeAll()
    84  
    85  		for i := 1; i < 10; i++ {
    86  			writeSequence(sampler, i, lvl)
    87  		}
    88  		assertSequence(t, logs.TakeAll(), lvl, 1, 2, 5, 8)
    89  	}
    90  }
    91  
    92  func TestLevelOfSampler(t *testing.T) {
    93  	levels := []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel}
    94  	for _, lvl := range levels {
    95  		lvl := lvl
    96  		t.Run(lvl.String(), func(t *testing.T) {
    97  			t.Parallel()
    98  
    99  			sampler, _ := fakeSampler(lvl, time.Minute, 2, 3)
   100  			assert.Equal(t, lvl, LevelOf(sampler), "Sampler level did not match.")
   101  		})
   102  	}
   103  }
   104  
   105  func TestSamplerDisabledLevels(t *testing.T) {
   106  	sampler, logs := fakeSampler(InfoLevel, time.Minute, 1, 100)
   107  
   108  	// Shouldn't be counted, because debug logging isn't enabled.
   109  	writeSequence(sampler, 1, DebugLevel)
   110  	writeSequence(sampler, 2, InfoLevel)
   111  	assertSequence(t, logs.TakeAll(), InfoLevel, 2)
   112  }
   113  
   114  func TestSamplerTicking(t *testing.T) {
   115  	// Ensure that we're resetting the sampler's counter every tick.
   116  	sampler, logs := fakeSampler(DebugLevel, 10*time.Millisecond, 5, 10)
   117  
   118  	// If we log five or fewer messages every tick, none of them should be
   119  	// dropped.
   120  	for tick := 0; tick < 2; tick++ {
   121  		for i := 1; i <= 5; i++ {
   122  			writeSequence(sampler, i, InfoLevel)
   123  		}
   124  		ztest.Sleep(15 * time.Millisecond)
   125  	}
   126  	assertSequence(
   127  		t,
   128  		logs.TakeAll(),
   129  		InfoLevel,
   130  		1, 2, 3, 4, 5, // first tick
   131  		1, 2, 3, 4, 5, // second tick
   132  	)
   133  
   134  	// If we log quickly, we should drop some logs. The first five statements
   135  	// each tick should be logged, then every tenth.
   136  	for tick := 0; tick < 3; tick++ {
   137  		for i := 1; i < 18; i++ {
   138  			writeSequence(sampler, i, InfoLevel)
   139  		}
   140  		ztest.Sleep(10 * time.Millisecond)
   141  	}
   142  
   143  	assertSequence(
   144  		t,
   145  		logs.TakeAll(),
   146  		InfoLevel,
   147  		1, 2, 3, 4, 5, 15, // first tick
   148  		1, 2, 3, 4, 5, 15, // second tick
   149  		1, 2, 3, 4, 5, 15, // third tick
   150  	)
   151  }
   152  
   153  type countingCore struct {
   154  	logs atomic.Uint32
   155  }
   156  
   157  func (c *countingCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry {
   158  	return ce.AddCore(ent, c)
   159  }
   160  
   161  func (c *countingCore) Write(Entry, []Field) error {
   162  	c.logs.Add(1)
   163  	return nil
   164  }
   165  
   166  func (c *countingCore) With([]Field) Core { return c }
   167  func (*countingCore) Fields() []Field     { return nil }
   168  func (*countingCore) Enabled(Level) bool  { return true }
   169  func (*countingCore) Sync() error         { return nil }
   170  
   171  func TestSamplerConcurrent(t *testing.T) {
   172  	const (
   173  		logsPerTick   = 10
   174  		numMessages   = 5
   175  		numTicks      = 25
   176  		numGoroutines = 10
   177  		tick          = 10 * time.Millisecond
   178  
   179  		// We'll make a total of,
   180  		// (numGoroutines * numTicks * logsPerTick * 2) log attempts
   181  		// with numMessages unique messages.
   182  		numLogAttempts = numGoroutines * logsPerTick * numTicks * 2
   183  		// Of those, we'll accept (logsPerTick * numTicks) entries
   184  		// for each unique message.
   185  		expectedCount = numMessages * logsPerTick * numTicks
   186  		// The rest will be dropped.
   187  		expectedDropped = numLogAttempts - expectedCount
   188  	)
   189  
   190  	clock := ztest.NewMockClock()
   191  
   192  	cc := &countingCore{}
   193  
   194  	hook, dropped, sampled := makeSamplerCountingHook()
   195  	sampler := NewSamplerWithOptions(cc, tick, logsPerTick, 100000, SamplerHook(hook))
   196  
   197  	stop := make(chan struct{})
   198  	var wg sync.WaitGroup
   199  	for i := 0; i < numGoroutines; i++ {
   200  		wg.Add(1)
   201  		go func(i int, ticker *time.Ticker) {
   202  			defer wg.Done()
   203  			defer ticker.Stop()
   204  
   205  			for {
   206  				select {
   207  				case <-stop:
   208  					return
   209  
   210  				case <-ticker.C:
   211  					for j := 0; j < logsPerTick*2; j++ {
   212  						msg := fmt.Sprintf("msg%v", i%numMessages)
   213  						ent := Entry{
   214  							Level:   DebugLevel,
   215  							Message: msg,
   216  							Time:    clock.Now(),
   217  						}
   218  						if ce := sampler.Check(ent, nil); ce != nil {
   219  							ce.Write()
   220  						}
   221  
   222  						// Give a chance for other goroutines to run.
   223  						runtime.Gosched()
   224  					}
   225  				}
   226  			}
   227  		}(i, clock.NewTicker(tick))
   228  	}
   229  
   230  	clock.Add(tick * numTicks)
   231  	close(stop)
   232  	wg.Wait()
   233  
   234  	assert.Equal(
   235  		t,
   236  		expectedCount,
   237  		int(cc.logs.Load()),
   238  		"Unexpected number of logs",
   239  	)
   240  	assert.Equal(t,
   241  		expectedCount,
   242  		int(sampled.Load()),
   243  		"Unexpected number of logs sampled",
   244  	)
   245  	assert.Equal(t,
   246  		expectedDropped,
   247  		int(dropped.Load()),
   248  		"Unexpected number of logs dropped",
   249  	)
   250  }
   251  
   252  func TestSamplerRaces(t *testing.T) {
   253  	sampler, _ := fakeSampler(DebugLevel, time.Minute, 1, 1000)
   254  
   255  	var wg sync.WaitGroup
   256  	start := make(chan struct{})
   257  
   258  	for i := 0; i < 100; i++ {
   259  		wg.Add(1)
   260  		go func() {
   261  			<-start
   262  			for j := 0; j < 100; j++ {
   263  				writeSequence(sampler, j, InfoLevel)
   264  			}
   265  			wg.Done()
   266  		}()
   267  	}
   268  
   269  	close(start)
   270  	wg.Wait()
   271  }
   272  
   273  func TestSamplerUnknownLevels(t *testing.T) {
   274  	// Prove that out-of-bounds levels don't panic.
   275  	unknownLevels := []Level{
   276  		DebugLevel - 1,
   277  		FatalLevel + 1,
   278  	}
   279  
   280  	for _, lvl := range unknownLevels {
   281  		t.Run(lvl.String(), func(t *testing.T) {
   282  			sampler, logs := fakeSampler(lvl, time.Minute, 2, 3)
   283  			for i := 1; i < 10; i++ {
   284  				writeSequence(sampler, i, lvl)
   285  			}
   286  
   287  			// Expect no sampling for unknown levels.
   288  			assertSequence(t, logs.TakeAll(), lvl, 1, 2, 3, 4, 5, 6, 7, 8, 9)
   289  		})
   290  	}
   291  }
   292  
   293  func TestSamplerWithZeroThereafter(t *testing.T) {
   294  	var counter countingCore
   295  
   296  	// Logs two messages per second.
   297  	sampler := NewSamplerWithOptions(&counter, time.Second, 2, 0)
   298  
   299  	now := time.Now()
   300  
   301  	for i := 0; i < 1000; i++ {
   302  		ent := Entry{
   303  			Level:   InfoLevel,
   304  			Message: "msg",
   305  			Time:    now,
   306  		}
   307  		if ce := sampler.Check(ent, nil); ce != nil {
   308  			ce.Write()
   309  		}
   310  	}
   311  
   312  	assert.Equal(t, 2, int(counter.logs.Load()),
   313  		"Unexpected number of logs")
   314  
   315  	now = now.Add(time.Second)
   316  
   317  	for i := 0; i < 1000; i++ {
   318  		ent := Entry{
   319  			Level:   InfoLevel,
   320  			Message: "msg",
   321  			Time:    now,
   322  		}
   323  		if ce := sampler.Check(ent, nil); ce != nil {
   324  			ce.Write()
   325  		}
   326  	}
   327  
   328  	assert.Equal(t, 4, int(counter.logs.Load()),
   329  		"Unexpected number of logs")
   330  }