github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/logger/loggerutils/logfile_test.go (about) 1 package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils" 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "strings" 12 "testing" 13 "text/tabwriter" 14 "time" 15 16 "github.com/docker/docker/daemon/logger" 17 "github.com/docker/docker/pkg/tailfile" 18 "gotest.tools/v3/assert" 19 "gotest.tools/v3/poll" 20 ) 21 22 type testDecoder struct { 23 rdr io.Reader 24 scanner *bufio.Scanner 25 resetCount int 26 } 27 28 func (d *testDecoder) Decode() (*logger.Message, error) { 29 if d.scanner == nil { 30 d.scanner = bufio.NewScanner(d.rdr) 31 } 32 if !d.scanner.Scan() { 33 return nil, d.scanner.Err() 34 } 35 // some comment 36 return &logger.Message{Line: d.scanner.Bytes(), Timestamp: time.Now()}, nil 37 } 38 39 func (d *testDecoder) Reset(rdr io.Reader) { 40 d.rdr = rdr 41 d.scanner = bufio.NewScanner(rdr) 42 d.resetCount++ 43 } 44 45 func (d *testDecoder) Close() { 46 d.rdr = nil 47 d.scanner = nil 48 } 49 50 func TestTailFiles(t *testing.T) { 51 s1 := strings.NewReader("Hello.\nMy name is Inigo Montoya.\n") 52 s2 := strings.NewReader("I'm serious.\nDon't call me Shirley!\n") 53 s3 := strings.NewReader("Roads?\nWhere we're going we don't need roads.\n") 54 55 files := []SizeReaderAt{s1, s2, s3} 56 watcher := logger.NewLogWatcher() 57 defer watcher.ConsumerGone() 58 59 tailReader := func(ctx context.Context, r SizeReaderAt, lines int) (io.Reader, int, error) { 60 return tailfile.NewTailReader(ctx, r, lines) 61 } 62 dec := &testDecoder{} 63 64 for desc, config := range map[string]logger.ReadConfig{} { 65 t.Run(desc, func(t *testing.T) { 66 started := make(chan struct{}) 67 go func() { 68 close(started) 69 tailFiles(files, watcher, dec, tailReader, config) 70 }() 71 <-started 72 }) 73 } 74 75 config := logger.ReadConfig{Tail: 2} 76 started := make(chan struct{}) 77 go func() { 78 close(started) 79 tailFiles(files, watcher, dec, tailReader, config) 80 }() 81 <-started 82 83 select { 84 case <-time.After(60 * time.Second): 85 t.Fatal("timeout waiting for tail line") 86 case err := <-watcher.Err: 87 assert.NilError(t, err) 88 case msg := <-watcher.Msg: 89 assert.Assert(t, msg != nil) 90 assert.Assert(t, string(msg.Line) == "Roads?", string(msg.Line)) 91 } 92 93 select { 94 case <-time.After(60 * time.Second): 95 t.Fatal("timeout waiting for tail line") 96 case err := <-watcher.Err: 97 assert.NilError(t, err) 98 case msg := <-watcher.Msg: 99 assert.Assert(t, msg != nil) 100 assert.Assert(t, string(msg.Line) == "Where we're going we don't need roads.", string(msg.Line)) 101 } 102 } 103 104 type dummyDecoder struct{} 105 106 func (dummyDecoder) Decode() (*logger.Message, error) { 107 return &logger.Message{}, nil 108 } 109 110 func (dummyDecoder) Close() {} 111 func (dummyDecoder) Reset(io.Reader) {} 112 113 func TestCheckCapacityAndRotate(t *testing.T) { 114 dir := t.TempDir() 115 116 logPath := filepath.Join(dir, "log") 117 getTailReader := func(ctx context.Context, r SizeReaderAt, lines int) (io.Reader, int, error) { 118 return tailfile.NewTailReader(ctx, r, lines) 119 } 120 createDecoder := func(io.Reader) Decoder { 121 return dummyDecoder{} 122 } 123 l, err := NewLogFile( 124 logPath, 125 5, // capacity 126 3, // maxFiles 127 true, // compress 128 createDecoder, 129 0600, // perms 130 getTailReader, 131 ) 132 assert.NilError(t, err) 133 defer l.Close() 134 135 ls := dirStringer{dir} 136 137 timestamp := time.Time{} 138 139 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 140 _, err = os.Stat(logPath + ".1") 141 assert.Assert(t, os.IsNotExist(err), ls) 142 143 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 144 poll.WaitOn(t, checkFileExists(logPath+".1.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 145 146 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 147 poll.WaitOn(t, checkFileExists(logPath+".1.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 148 poll.WaitOn(t, checkFileExists(logPath+".2.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 149 150 t.Run("closed log file", func(t *testing.T) { 151 // Now let's simulate a failed rotation where the file was able to be closed but something else happened elsewhere 152 // down the line. 153 // We want to make sure that we can recover in the case that `l.f` was closed while attempting a rotation. 154 l.f.Close() 155 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 156 assert.NilError(t, os.Remove(logPath+".2.gz")) 157 }) 158 159 t.Run("with log reader", func(t *testing.T) { 160 // Make sure rotate works with an active reader 161 lw := l.ReadLogs(logger.ReadConfig{Follow: true, Tail: 1000}) 162 defer lw.ConsumerGone() 163 164 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 0!")), ls) 165 // make sure the log reader is primed 166 waitForMsg(t, lw, 30*time.Second) 167 168 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 1!")), ls) 169 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 2!")), ls) 170 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 3!")), ls) 171 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 4!")), ls) 172 poll.WaitOn(t, checkFileExists(logPath+".2.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 173 }) 174 } 175 176 func waitForMsg(t *testing.T, lw *logger.LogWatcher, timeout time.Duration) { 177 t.Helper() 178 179 timer := time.NewTimer(timeout) 180 defer timer.Stop() 181 182 select { 183 case _, ok := <-lw.Msg: 184 assert.Assert(t, ok, "log producer gone before log message arrived") 185 case err := <-lw.Err: 186 assert.NilError(t, err) 187 case <-timer.C: 188 t.Fatal("timeout waiting for log message") 189 } 190 } 191 192 type dirStringer struct { 193 d string 194 } 195 196 func (d dirStringer) String() string { 197 ls, err := os.ReadDir(d.d) 198 if err != nil { 199 return "" 200 } 201 buf := bytes.NewBuffer(nil) 202 tw := tabwriter.NewWriter(buf, 1, 8, 1, '\t', 0) 203 buf.WriteString("\n") 204 205 btw := bufio.NewWriter(tw) 206 207 for _, entry := range ls { 208 fi, err := entry.Info() 209 if err != nil { 210 return "" 211 } 212 213 btw.WriteString(fmt.Sprintf("%s\t%s\t%dB\t%s\n", fi.Name(), fi.Mode(), fi.Size(), fi.ModTime())) 214 } 215 btw.Flush() 216 tw.Flush() 217 return buf.String() 218 } 219 220 func checkFileExists(name string) poll.Check { 221 return func(t poll.LogT) poll.Result { 222 _, err := os.Stat(name) 223 switch { 224 case err == nil: 225 return poll.Success() 226 case os.IsNotExist(err): 227 return poll.Continue("waiting for %s to exist", name) 228 default: 229 t.Logf("waiting for %s: %v: %s", name, err, dirStringer{filepath.Dir(name)}) 230 return poll.Error(err) 231 } 232 } 233 }