github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/logger/buf_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package logger_test
     5  
     6  import (
     7  	"errors"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	corelogger "github.com/juju/juju/core/logger"
    16  	coretesting "github.com/juju/juju/testing"
    17  )
    18  
    19  type BufferedLoggerSuite struct {
    20  	testing.IsolationSuite
    21  }
    22  
    23  var _ = gc.Suite(&BufferedLoggerSuite{})
    24  
    25  func (s *BufferedLoggerSuite) SetUpTest(c *gc.C) {
    26  	s.IsolationSuite.SetUpTest(c)
    27  }
    28  
    29  func (s *BufferedLoggerSuite) waitFlush(c *gc.C, mock *mockLogger) []corelogger.LogRecord {
    30  	select {
    31  	case records := <-mock.called:
    32  		return records
    33  	case <-time.After(coretesting.LongWait):
    34  	}
    35  	c.Fatal("timed out waiting for logs to be flushed")
    36  	panic("unreachable")
    37  }
    38  
    39  func (s *BufferedLoggerSuite) assertNoFlush(c *gc.C, mock *mockLogger, clock *testclock.Clock) {
    40  	err := clock.WaitAdvance(0, 0, 0) // There should be no active timers
    41  	c.Assert(err, jc.ErrorIsNil)
    42  	select {
    43  	case records := <-mock.called:
    44  		c.Fatalf("unexpected log records: %v", records)
    45  	case <-time.After(coretesting.ShortWait):
    46  	}
    47  }
    48  
    49  func (s *BufferedLoggerSuite) TestLogFlushes(c *gc.C) {
    50  	const bufsz = 3
    51  	mock := mockLogger{}
    52  	clock := testclock.NewClock(time.Time{})
    53  	b := corelogger.NewBufferedLogger(&mock, bufsz, time.Minute, clock)
    54  	in := []corelogger.LogRecord{{
    55  		Entity:  "not-a-tag",
    56  		Message: "foo",
    57  	}, {
    58  		Entity:  "not-a-tag",
    59  		Message: "bar",
    60  	}, {
    61  		Entity:  "not-a-tag",
    62  		Message: "baz",
    63  	}}
    64  
    65  	err := b.Log(in[:2])
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	mock.CheckNoCalls(c)
    68  
    69  	err = b.Log(in[2:])
    70  	c.Assert(err, jc.ErrorIsNil)
    71  	mock.CheckCalls(c, []testing.StubCall{
    72  		{"Log", []interface{}{in}},
    73  	})
    74  
    75  	err = clock.WaitAdvance(0, coretesting.LongWait, 0)
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	s.assertNoFlush(c, &mock, clock)
    78  }
    79  
    80  func (s *BufferedLoggerSuite) TestLogFlushesMultiple(c *gc.C) {
    81  	const bufsz = 1
    82  	mock := mockLogger{}
    83  	clock := testclock.NewClock(time.Time{})
    84  	b := corelogger.NewBufferedLogger(&mock, bufsz, time.Minute, clock)
    85  	in := []corelogger.LogRecord{{
    86  		Entity:  "not-a-tag",
    87  		Message: "foo",
    88  	}, {
    89  		Entity:  "not-a-tag",
    90  		Message: "bar",
    91  	}, {
    92  		Entity:  "not-a-tag",
    93  		Message: "baz",
    94  	}}
    95  
    96  	err := b.Log(in)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	mock.CheckCalls(c, []testing.StubCall{
    99  		{"Log", []interface{}{in[:1]}},
   100  		{"Log", []interface{}{in[1:2]}},
   101  		{"Log", []interface{}{in[2:]}},
   102  	})
   103  }
   104  
   105  func (s *BufferedLoggerSuite) TestTimerFlushes(c *gc.C) {
   106  	const bufsz = 10
   107  	const flushInterval = time.Minute
   108  	mock := mockLogger{called: make(chan []corelogger.LogRecord)}
   109  	clock := testclock.NewClock(time.Time{})
   110  
   111  	b := corelogger.NewBufferedLogger(&mock, bufsz, flushInterval, clock)
   112  	in := []corelogger.LogRecord{{
   113  		Entity:  "not-a-tag",
   114  		Message: "foo",
   115  	}, {
   116  		Entity:  "not-a-tag",
   117  		Message: "bar",
   118  	}}
   119  
   120  	err := b.Log(in[:1])
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	mock.CheckNoCalls(c)
   123  
   124  	// Advance, but not far enough to trigger the flush.
   125  	clock.WaitAdvance(30*time.Second, coretesting.LongWait, 1)
   126  	mock.CheckNoCalls(c)
   127  
   128  	// Log again; the timer should not have been reset.
   129  	err = b.Log(in[1:])
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	mock.CheckNoCalls(c)
   132  
   133  	// Advance to to the flush interval.
   134  	clock.Advance(30 * time.Second)
   135  	c.Assert(s.waitFlush(c, &mock), jc.DeepEquals, in)
   136  	mock.CheckCalls(c, []testing.StubCall{
   137  		{"Log", []interface{}{in}},
   138  	})
   139  	s.assertNoFlush(c, &mock, clock)
   140  	mock.ResetCalls()
   141  
   142  	// Logging again, the timer resets to the time at which
   143  	// the new log records are inserted.
   144  	err = b.Log(in)
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	clock.WaitAdvance(59*time.Second, coretesting.LongWait, 1)
   147  	mock.CheckNoCalls(c)
   148  	clock.Advance(1 * time.Second)
   149  	c.Assert(s.waitFlush(c, &mock), jc.DeepEquals, in)
   150  	mock.CheckCalls(c, []testing.StubCall{
   151  		{"Log", []interface{}{in}},
   152  	})
   153  	s.assertNoFlush(c, &mock, clock)
   154  }
   155  
   156  func (s *BufferedLoggerSuite) TestLogOverCapacity(c *gc.C) {
   157  	const bufsz = 2
   158  	const flushInterval = time.Minute
   159  	mock := mockLogger{called: make(chan []corelogger.LogRecord, 1)}
   160  	clock := testclock.NewClock(time.Time{})
   161  
   162  	// The buffer has a capacity of 2, so writing 3 logs will
   163  	// cause 2 to be flushed, with 1 remaining in the buffer
   164  	// until the timer triggers.
   165  	b := corelogger.NewBufferedLogger(&mock, bufsz, flushInterval, clock)
   166  	in := []corelogger.LogRecord{{
   167  		Entity:  "not-a-tag",
   168  		Message: "foo",
   169  	}, {
   170  		Entity:  "not-a-tag",
   171  		Message: "bar",
   172  	}, {
   173  		Entity:  "not-a-tag",
   174  		Message: "baz",
   175  	}}
   176  
   177  	err := b.Log(in)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	c.Assert(s.waitFlush(c, &mock), jc.DeepEquals, in[:bufsz])
   180  
   181  	clock.WaitAdvance(time.Minute, coretesting.LongWait, 1)
   182  	c.Assert(s.waitFlush(c, &mock), jc.DeepEquals, in[bufsz:])
   183  
   184  	mock.CheckCalls(c, []testing.StubCall{
   185  		{"Log", []interface{}{in[:bufsz]}},
   186  		{"Log", []interface{}{in[bufsz:]}},
   187  	})
   188  }
   189  
   190  func (s *BufferedLoggerSuite) TestFlushNothing(c *gc.C) {
   191  	mock := mockLogger{}
   192  	clock := testclock.NewClock(time.Time{})
   193  	b := corelogger.NewBufferedLogger(&mock, 1, time.Minute, clock)
   194  	err := b.Flush()
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	mock.CheckNoCalls(c)
   197  }
   198  
   199  func (s *BufferedLoggerSuite) TestFlushReportsError(c *gc.C) {
   200  	mock := mockLogger{}
   201  	clock := testclock.NewClock(time.Time{})
   202  	mock.SetErrors(errors.New("nope"))
   203  	b := corelogger.NewBufferedLogger(&mock, 2, time.Minute, clock)
   204  	err := b.Log([]corelogger.LogRecord{{
   205  		Entity:  "not-a-tag",
   206  		Message: "foo",
   207  	}})
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	err = b.Flush()
   210  	c.Assert(err, gc.ErrorMatches, "nope")
   211  }
   212  
   213  func (s *BufferedLoggerSuite) TestLogReportsError(c *gc.C) {
   214  	mock := mockLogger{}
   215  	clock := testclock.NewClock(time.Time{})
   216  	mock.SetErrors(errors.New("nope"))
   217  	b := corelogger.NewBufferedLogger(&mock, 1, time.Minute, clock)
   218  	err := b.Log([]corelogger.LogRecord{{
   219  		Entity:  "not-a-tag",
   220  		Message: "foo",
   221  	}})
   222  	c.Assert(err, gc.ErrorMatches, "nope")
   223  }
   224  
   225  type mockLogger struct {
   226  	testing.Stub
   227  	called chan []corelogger.LogRecord
   228  }
   229  
   230  func (m *mockLogger) Log(in []corelogger.LogRecord) error {
   231  	incopy := make([]corelogger.LogRecord, len(in))
   232  	copy(incopy, in)
   233  	m.MethodCall(m, "Log", incopy)
   234  	if m.called != nil {
   235  		m.called <- incopy
   236  	}
   237  	return m.NextErr()
   238  }