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 }