github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/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 fwd := newForwarder(config) 68 go func() { 69 close(started) 70 tailFiles(files, watcher, dec, tailReader, config.Tail, fwd) 71 }() 72 <-started 73 }) 74 } 75 76 config := logger.ReadConfig{Tail: 2} 77 fwd := newForwarder(config) 78 started := make(chan struct{}) 79 go func() { 80 close(started) 81 tailFiles(files, watcher, dec, tailReader, config.Tail, fwd) 82 }() 83 <-started 84 85 select { 86 case <-time.After(60 * time.Second): 87 t.Fatal("timeout waiting for tail line") 88 case err := <-watcher.Err: 89 assert.NilError(t, err) 90 case msg := <-watcher.Msg: 91 assert.Assert(t, msg != nil) 92 assert.Assert(t, string(msg.Line) == "Roads?", string(msg.Line)) 93 } 94 95 select { 96 case <-time.After(60 * time.Second): 97 t.Fatal("timeout waiting for tail line") 98 case err := <-watcher.Err: 99 assert.NilError(t, err) 100 case msg := <-watcher.Msg: 101 assert.Assert(t, msg != nil) 102 assert.Assert(t, string(msg.Line) == "Where we're going we don't need roads.", string(msg.Line)) 103 } 104 } 105 106 type dummyDecoder struct{} 107 108 func (dummyDecoder) Decode() (*logger.Message, error) { 109 return &logger.Message{}, nil 110 } 111 112 func (dummyDecoder) Close() {} 113 func (dummyDecoder) Reset(io.Reader) {} 114 115 func TestCheckCapacityAndRotate(t *testing.T) { 116 dir := t.TempDir() 117 118 logPath := filepath.Join(dir, "log") 119 getTailReader := func(ctx context.Context, r SizeReaderAt, lines int) (io.Reader, int, error) { 120 return tailfile.NewTailReader(ctx, r, lines) 121 } 122 createDecoder := func(io.Reader) Decoder { 123 return dummyDecoder{} 124 } 125 l, err := NewLogFile( 126 logPath, 127 5, // capacity 128 3, // maxFiles 129 true, // compress 130 createDecoder, 131 0600, // perms 132 getTailReader, 133 ) 134 assert.NilError(t, err) 135 defer l.Close() 136 137 ls := dirStringer{dir} 138 139 timestamp := time.Time{} 140 141 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 142 _, err = os.Stat(logPath + ".1") 143 assert.Assert(t, os.IsNotExist(err), ls) 144 145 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 146 poll.WaitOn(t, checkFileExists(logPath+".1.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 147 148 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 149 poll.WaitOn(t, checkFileExists(logPath+".1.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 150 poll.WaitOn(t, checkFileExists(logPath+".2.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 151 152 t.Run("closed log file", func(t *testing.T) { 153 // Now let's simulate a failed rotation where the file was able to be closed but something else happened elsewhere 154 // down the line. 155 // We want to make sure that we can recover in the case that `l.f` was closed while attempting a rotation. 156 l.f.Close() 157 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world!"))) 158 assert.NilError(t, os.Remove(logPath+".2.gz")) 159 }) 160 161 t.Run("with log reader", func(t *testing.T) { 162 // Make sure rotate works with an active reader 163 lw := l.ReadLogs(logger.ReadConfig{Follow: true, Tail: 1000}) 164 defer lw.ConsumerGone() 165 166 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 0!")), ls) 167 // make sure the log reader is primed 168 waitForMsg(t, lw, 30*time.Second) 169 170 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 1!")), ls) 171 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 2!")), ls) 172 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 3!")), ls) 173 assert.NilError(t, l.WriteLogEntry(timestamp, []byte("hello world 4!")), ls) 174 poll.WaitOn(t, checkFileExists(logPath+".2.gz"), poll.WithDelay(time.Millisecond), poll.WithTimeout(30*time.Second)) 175 }) 176 } 177 178 func waitForMsg(t *testing.T, lw *logger.LogWatcher, timeout time.Duration) { 179 t.Helper() 180 181 timer := time.NewTimer(timeout) 182 defer timer.Stop() 183 184 select { 185 case _, ok := <-lw.Msg: 186 assert.Assert(t, ok, "log producer gone before log message arrived") 187 case err := <-lw.Err: 188 assert.NilError(t, err) 189 case <-timer.C: 190 t.Fatal("timeout waiting for log message") 191 } 192 } 193 194 type dirStringer struct { 195 d string 196 } 197 198 func (d dirStringer) String() string { 199 ls, err := os.ReadDir(d.d) 200 if err != nil { 201 return "" 202 } 203 buf := bytes.NewBuffer(nil) 204 tw := tabwriter.NewWriter(buf, 1, 8, 1, '\t', 0) 205 buf.WriteString("\n") 206 207 btw := bufio.NewWriter(tw) 208 209 for _, entry := range ls { 210 fi, err := entry.Info() 211 if err != nil { 212 return "" 213 } 214 215 btw.WriteString(fmt.Sprintf("%s\t%s\t%dB\t%s\n", fi.Name(), fi.Mode(), fi.Size(), fi.ModTime())) 216 } 217 btw.Flush() 218 tw.Flush() 219 return buf.String() 220 } 221 222 func checkFileExists(name string) poll.Check { 223 return func(t poll.LogT) poll.Result { 224 _, err := os.Stat(name) 225 switch { 226 case err == nil: 227 return poll.Success() 228 case os.IsNotExist(err): 229 return poll.Continue("waiting for %s to exist", name) 230 default: 231 t.Logf("waiting for %s: %v: %s", name, err, dirStringer{filepath.Dir(name)}) 232 return poll.Error(err) 233 } 234 } 235 }