github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/logger/loggertest/logreader.go (about) 1 package loggertest // import "github.com/docker/docker/daemon/logger/loggertest" 2 3 import ( 4 "runtime" 5 "strings" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 "gotest.tools/v3/assert" 13 14 "github.com/docker/docker/api/types/backend" 15 "github.com/docker/docker/daemon/logger" 16 ) 17 18 // Reader tests that a logger.LogReader implementation behaves as it should. 19 type Reader struct { 20 // Factory returns a function which constructs loggers for the container 21 // specified in info. Each call to the returned function must yield a 22 // distinct logger instance which can read back logs written by earlier 23 // instances. 24 Factory func(*testing.T, logger.Info) func(*testing.T) logger.Logger 25 } 26 27 var compareLog cmp.Options = []cmp.Option{ 28 // The json-log driver does not round-trip PLogMetaData and API users do 29 // not expect it. 30 cmpopts.IgnoreFields(logger.Message{}, "PLogMetaData"), 31 cmp.Transformer("string", func(b []byte) string { return string(b) }), 32 } 33 34 // TestTail tests the behavior of the LogReader's tail implementation. 35 func (tr Reader) TestTail(t *testing.T) { 36 t.Run("Live", func(t *testing.T) { tr.testTail(t, true) }) 37 t.Run("LiveEmpty", func(t *testing.T) { tr.testTailEmptyLogs(t, true) }) 38 t.Run("Stopped", func(t *testing.T) { tr.testTail(t, false) }) 39 t.Run("StoppedEmpty", func(t *testing.T) { tr.testTailEmptyLogs(t, false) }) 40 } 41 42 func makeTestMessages() []*logger.Message { 43 return []*logger.Message{ 44 {Source: "stdout", Timestamp: time.Now().Add(-1 * 30 * time.Minute), Line: []byte("a message")}, 45 {Source: "stdout", Timestamp: time.Now().Add(-1 * 20 * time.Minute), Line: []byte("another message"), PLogMetaData: &backend.PartialLogMetaData{ID: "aaaaaaaa", Ordinal: 1, Last: true}}, 46 {Source: "stderr", Timestamp: time.Now().Add(-1 * 15 * time.Minute), Line: []byte("to be..."), PLogMetaData: &backend.PartialLogMetaData{ID: "bbbbbbbb", Ordinal: 1}}, 47 {Source: "stderr", Timestamp: time.Now().Add(-1 * 15 * time.Minute), Line: []byte("continued"), PLogMetaData: &backend.PartialLogMetaData{ID: "bbbbbbbb", Ordinal: 2, Last: true}}, 48 {Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: []byte("a really long message " + strings.Repeat("a", 4096))}, 49 {Source: "stderr", Timestamp: time.Now().Add(-1 * 10 * time.Minute), Line: []byte("just one more message")}, 50 } 51 } 52 53 func (tr Reader) testTail(t *testing.T, live bool) { 54 t.Parallel() 55 factory := tr.Factory(t, logger.Info{ 56 ContainerID: "tailtest0000", 57 ContainerName: "logtail", 58 }) 59 l := factory(t) 60 if live { 61 defer func() { assert.NilError(t, l.Close()) }() 62 } 63 64 mm := makeTestMessages() 65 expected := logMessages(t, l, mm) 66 67 if !live { 68 // Simulate reading from a stopped container. 69 assert.NilError(t, l.Close()) 70 l = factory(t) 71 defer func() { assert.NilError(t, l.Close()) }() 72 } 73 lr := l.(logger.LogReader) 74 75 t.Run("Exact", func(t *testing.T) { 76 t.Parallel() 77 lw := lr.ReadLogs(logger.ReadConfig{Tail: len(mm)}) 78 defer lw.ConsumerGone() 79 assert.DeepEqual(t, readAll(t, lw), expected, compareLog) 80 }) 81 82 t.Run("LessThanAvailable", func(t *testing.T) { 83 t.Parallel() 84 lw := lr.ReadLogs(logger.ReadConfig{Tail: 2}) 85 defer lw.ConsumerGone() 86 assert.DeepEqual(t, readAll(t, lw), expected[len(mm)-2:], compareLog) 87 }) 88 89 t.Run("MoreThanAvailable", func(t *testing.T) { 90 t.Parallel() 91 lw := lr.ReadLogs(logger.ReadConfig{Tail: 100}) 92 defer lw.ConsumerGone() 93 assert.DeepEqual(t, readAll(t, lw), expected, compareLog) 94 }) 95 96 t.Run("All", func(t *testing.T) { 97 t.Parallel() 98 lw := lr.ReadLogs(logger.ReadConfig{Tail: -1}) 99 defer lw.ConsumerGone() 100 assert.DeepEqual(t, readAll(t, lw), expected, compareLog) 101 }) 102 103 t.Run("Since", func(t *testing.T) { 104 t.Parallel() 105 lw := lr.ReadLogs(logger.ReadConfig{Tail: -1, Since: mm[1].Timestamp.Truncate(time.Millisecond)}) 106 defer lw.ConsumerGone() 107 assert.DeepEqual(t, readAll(t, lw), expected[1:], compareLog) 108 }) 109 110 t.Run("MoreThanSince", func(t *testing.T) { 111 t.Parallel() 112 lw := lr.ReadLogs(logger.ReadConfig{Tail: len(mm), Since: mm[1].Timestamp.Truncate(time.Millisecond)}) 113 defer lw.ConsumerGone() 114 assert.DeepEqual(t, readAll(t, lw), expected[1:], compareLog) 115 }) 116 117 t.Run("LessThanSince", func(t *testing.T) { 118 t.Parallel() 119 lw := lr.ReadLogs(logger.ReadConfig{Tail: len(mm) - 2, Since: mm[1].Timestamp.Truncate(time.Millisecond)}) 120 defer lw.ConsumerGone() 121 assert.DeepEqual(t, readAll(t, lw), expected[2:], compareLog) 122 }) 123 124 t.Run("Until", func(t *testing.T) { 125 t.Parallel() 126 lw := lr.ReadLogs(logger.ReadConfig{Tail: -1, Until: mm[2].Timestamp.Add(-time.Millisecond)}) 127 defer lw.ConsumerGone() 128 assert.DeepEqual(t, readAll(t, lw), expected[:2], compareLog) 129 }) 130 131 t.Run("SinceAndUntil", func(t *testing.T) { 132 t.Parallel() 133 lw := lr.ReadLogs(logger.ReadConfig{Tail: -1, Since: mm[1].Timestamp.Truncate(time.Millisecond), Until: mm[1].Timestamp.Add(time.Millisecond)}) 134 defer lw.ConsumerGone() 135 assert.DeepEqual(t, readAll(t, lw), expected[1:2], compareLog) 136 }) 137 } 138 139 func (tr Reader) testTailEmptyLogs(t *testing.T, live bool) { 140 t.Parallel() 141 factory := tr.Factory(t, logger.Info{ 142 ContainerID: "tailemptytest", 143 ContainerName: "logtail", 144 }) 145 l := factory(t) 146 if !live { 147 assert.NilError(t, l.Close()) 148 l = factory(t) 149 } 150 defer func() { assert.NilError(t, l.Close()) }() 151 152 for _, tt := range []struct { 153 name string 154 cfg logger.ReadConfig 155 }{ 156 {name: "Zero", cfg: logger.ReadConfig{}}, 157 {name: "All", cfg: logger.ReadConfig{Tail: -1}}, 158 {name: "Tail", cfg: logger.ReadConfig{Tail: 42}}, 159 {name: "Since", cfg: logger.ReadConfig{Since: time.Unix(1, 0)}}, 160 {name: "Until", cfg: logger.ReadConfig{Until: time.Date(2100, time.January, 1, 1, 1, 1, 0, time.UTC)}}, 161 {name: "SinceAndUntil", cfg: logger.ReadConfig{Since: time.Unix(1, 0), Until: time.Date(2100, time.January, 1, 1, 1, 1, 0, time.UTC)}}, 162 } { 163 tt := tt 164 t.Run(tt.name, func(t *testing.T) { 165 t.Parallel() 166 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{}) 167 defer lw.ConsumerGone() 168 assert.DeepEqual(t, readAll(t, lw), ([]*logger.Message)(nil), cmpopts.EquateEmpty()) 169 }) 170 } 171 } 172 173 // TestFollow tests the LogReader's follow implementation. 174 // 175 // The LogReader is expected to be able to follow an arbitrary number of 176 // messages at a high rate with no dropped messages. 177 func (tr Reader) TestFollow(t *testing.T) { 178 // Reader sends all logs and closes after logger is closed 179 // - Starting from empty log (like run) 180 t.Run("FromEmptyLog", func(t *testing.T) { 181 t.Parallel() 182 l := tr.Factory(t, logger.Info{ 183 ContainerID: "followstart0", 184 ContainerName: "logloglog", 185 })(t) 186 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true}) 187 defer lw.ConsumerGone() 188 189 doneReading := make(chan struct{}) 190 var logs []*logger.Message 191 go func() { 192 defer close(doneReading) 193 logs = readAll(t, lw) 194 }() 195 196 mm := makeTestMessages() 197 expected := logMessages(t, l, mm) 198 assert.NilError(t, l.Close()) 199 <-doneReading 200 assert.DeepEqual(t, logs, expected, compareLog) 201 }) 202 203 t.Run("AttachMidStream", func(t *testing.T) { 204 t.Parallel() 205 l := tr.Factory(t, logger.Info{ 206 ContainerID: "followmiddle", 207 ContainerName: "logloglog", 208 })(t) 209 210 mm := makeTestMessages() 211 expected := logMessages(t, l, mm[0:1]) 212 213 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true}) 214 defer lw.ConsumerGone() 215 216 doneReading := make(chan struct{}) 217 var logs []*logger.Message 218 go func() { 219 defer close(doneReading) 220 logs = readAll(t, lw) 221 }() 222 223 expected = append(expected, logMessages(t, l, mm[1:])...) 224 assert.NilError(t, l.Close()) 225 <-doneReading 226 assert.DeepEqual(t, logs, expected, compareLog) 227 }) 228 229 t.Run("Since", func(t *testing.T) { 230 t.Parallel() 231 l := tr.Factory(t, logger.Info{ 232 ContainerID: "followsince0", 233 ContainerName: "logloglog", 234 })(t) 235 236 mm := makeTestMessages() 237 238 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true, Since: mm[2].Timestamp.Truncate(time.Millisecond)}) 239 defer lw.ConsumerGone() 240 241 doneReading := make(chan struct{}) 242 var logs []*logger.Message 243 go func() { 244 defer close(doneReading) 245 logs = readAll(t, lw) 246 }() 247 248 expected := logMessages(t, l, mm)[2:] 249 assert.NilError(t, l.Close()) 250 <-doneReading 251 assert.DeepEqual(t, logs, expected, compareLog) 252 }) 253 254 t.Run("Until", func(t *testing.T) { 255 t.Parallel() 256 l := tr.Factory(t, logger.Info{ 257 ContainerID: "followuntil0", 258 ContainerName: "logloglog", 259 })(t) 260 261 mm := makeTestMessages() 262 263 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true, Until: mm[2].Timestamp.Add(-time.Millisecond)}) 264 defer lw.ConsumerGone() 265 266 doneReading := make(chan struct{}) 267 var logs []*logger.Message 268 go func() { 269 defer close(doneReading) 270 logs = readAll(t, lw) 271 }() 272 273 expected := logMessages(t, l, mm)[:2] 274 defer assert.NilError(t, l.Close()) // Reading should end before the logger is closed. 275 <-doneReading 276 assert.DeepEqual(t, logs, expected, compareLog) 277 }) 278 279 t.Run("SinceAndUntil", func(t *testing.T) { 280 t.Parallel() 281 l := tr.Factory(t, logger.Info{ 282 ContainerID: "followbounded", 283 ContainerName: "logloglog", 284 })(t) 285 286 mm := makeTestMessages() 287 288 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true, Since: mm[1].Timestamp.Add(-time.Millisecond), Until: mm[2].Timestamp.Add(-time.Millisecond)}) 289 defer lw.ConsumerGone() 290 291 doneReading := make(chan struct{}) 292 var logs []*logger.Message 293 go func() { 294 defer close(doneReading) 295 logs = readAll(t, lw) 296 }() 297 298 expected := logMessages(t, l, mm)[1:2] 299 defer assert.NilError(t, l.Close()) // Reading should end before the logger is closed. 300 <-doneReading 301 assert.DeepEqual(t, logs, expected, compareLog) 302 }) 303 304 t.Run("Tail=0", func(t *testing.T) { 305 t.Parallel() 306 l := tr.Factory(t, logger.Info{ 307 ContainerID: "followtail00", 308 ContainerName: "logloglog", 309 })(t) 310 311 mm := makeTestMessages() 312 logMessages(t, l, mm[0:2]) 313 314 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: 0, Follow: true}) 315 defer lw.ConsumerGone() 316 317 doneReading := make(chan struct{}) 318 var logs []*logger.Message 319 go func() { 320 defer close(doneReading) 321 logs = readAll(t, lw) 322 }() 323 324 expected := logMessages(t, l, mm[2:]) 325 assert.NilError(t, l.Close()) 326 <-doneReading 327 assert.DeepEqual(t, logs, expected, compareLog) 328 }) 329 330 t.Run("Tail>0", func(t *testing.T) { 331 t.Parallel() 332 l := tr.Factory(t, logger.Info{ 333 ContainerID: "followtail00", 334 ContainerName: "logloglog", 335 })(t) 336 337 mm := makeTestMessages() 338 expected := logMessages(t, l, mm[0:2])[1:] 339 340 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: 1, Follow: true}) 341 defer lw.ConsumerGone() 342 343 doneReading := make(chan struct{}) 344 var logs []*logger.Message 345 go func() { 346 defer close(doneReading) 347 logs = readAll(t, lw) 348 }() 349 350 expected = append(expected, logMessages(t, l, mm[2:])...) 351 assert.NilError(t, l.Close()) 352 <-doneReading 353 assert.DeepEqual(t, logs, expected, compareLog) 354 }) 355 356 t.Run("MultipleStarts", func(t *testing.T) { 357 t.Parallel() 358 factory := tr.Factory(t, logger.Info{ 359 ContainerID: "startrestart", 360 ContainerName: "startmeup", 361 }) 362 363 mm := makeTestMessages() 364 l := factory(t) 365 expected := logMessages(t, l, mm[:3]) 366 assert.NilError(t, l.Close()) 367 368 l = factory(t) 369 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Tail: -1, Follow: true}) 370 defer lw.ConsumerGone() 371 372 doneReading := make(chan struct{}) 373 var logs []*logger.Message 374 go func() { 375 defer close(doneReading) 376 logs = readAll(t, lw) 377 }() 378 379 expected = append(expected, logMessages(t, l, mm[3:])...) 380 assert.NilError(t, l.Close()) 381 <-doneReading 382 assert.DeepEqual(t, logs, expected, compareLog) 383 }) 384 385 t.Run("Concurrent", tr.TestConcurrent) 386 } 387 388 // TestConcurrent tests the Logger and its LogReader implementation for 389 // race conditions when logging from multiple goroutines concurrently. 390 func (tr Reader) TestConcurrent(t *testing.T) { 391 t.Parallel() 392 l := tr.Factory(t, logger.Info{ 393 ContainerID: "logconcurrent0", 394 ContainerName: "logconcurrent123", 395 })(t) 396 397 // Split test messages 398 stderrMessages := []*logger.Message{} 399 stdoutMessages := []*logger.Message{} 400 for _, m := range makeTestMessages() { 401 if m.Source == "stdout" { 402 stdoutMessages = append(stdoutMessages, m) 403 } else if m.Source == "stderr" { 404 stderrMessages = append(stderrMessages, m) 405 } 406 } 407 408 // Follow all logs 409 lw := l.(logger.LogReader).ReadLogs(logger.ReadConfig{Follow: true, Tail: -1}) 410 defer lw.ConsumerGone() 411 412 // Log concurrently from two sources and close log 413 wg := &sync.WaitGroup{} 414 logAll := func(msgs []*logger.Message) { 415 defer wg.Done() 416 for _, m := range msgs { 417 l.Log(copyLogMessage(m)) 418 } 419 } 420 421 closed := make(chan struct{}) 422 wg.Add(2) 423 go logAll(stdoutMessages) 424 go logAll(stderrMessages) 425 go func() { 426 defer close(closed) 427 defer l.Close() 428 wg.Wait() 429 }() 430 431 // Check if the message count, order and content is equal to what was logged 432 for { 433 l := readMessage(t, lw) 434 if l == nil { 435 break 436 } 437 438 var messages *[]*logger.Message 439 if l.Source == "stdout" { 440 messages = &stdoutMessages 441 } else if l.Source == "stderr" { 442 messages = &stderrMessages 443 } else { 444 t.Fatalf("Corrupted message.Source = %q", l.Source) 445 } 446 447 expectedMsg := transformToExpected((*messages)[0]) 448 449 assert.DeepEqual(t, *expectedMsg, *l, compareLog) 450 *messages = (*messages)[1:] 451 } 452 453 assert.Equal(t, len(stdoutMessages), 0) 454 assert.Equal(t, len(stderrMessages), 0) 455 456 // Make sure log gets closed before we return 457 // so the temporary dir can be deleted 458 <-closed 459 } 460 461 // logMessages logs messages to l and returns a slice of messages as would be 462 // expected to be read back. The message values are not modified and the 463 // returned slice of messages are deep-copied. 464 func logMessages(t *testing.T, l logger.Logger, messages []*logger.Message) []*logger.Message { 465 t.Helper() 466 var expected []*logger.Message 467 for _, m := range messages { 468 // Copy the log message because the underlying log writer resets 469 // the log message and returns it to a buffer pool. 470 assert.NilError(t, l.Log(copyLogMessage(m))) 471 runtime.Gosched() 472 473 expect := transformToExpected(m) 474 expected = append(expected, expect) 475 } 476 return expected 477 } 478 479 // Existing API consumers expect a newline to be appended to 480 // messages other than nonterminal partials as that matches the 481 // existing behavior of the json-file log driver. 482 func transformToExpected(m *logger.Message) *logger.Message { 483 // Copy the log message again so as not to mutate the input. 484 copy := copyLogMessage(m) 485 if m.PLogMetaData == nil || m.PLogMetaData.Last { 486 copy.Line = append(copy.Line, '\n') 487 } 488 489 return copy 490 } 491 492 func copyLogMessage(src *logger.Message) *logger.Message { 493 dst := logger.NewMessage() 494 dst.Source = src.Source 495 dst.Timestamp = src.Timestamp 496 dst.Attrs = src.Attrs 497 dst.Err = src.Err 498 dst.Line = append(dst.Line, src.Line...) 499 if src.PLogMetaData != nil { 500 lmd := *src.PLogMetaData 501 dst.PLogMetaData = &lmd 502 } 503 return dst 504 } 505 func readMessage(t *testing.T, lw *logger.LogWatcher) *logger.Message { 506 t.Helper() 507 timeout := time.NewTimer(5 * time.Second) 508 defer timeout.Stop() 509 select { 510 case <-timeout.C: 511 t.Error("timed out waiting for message") 512 return nil 513 case err, open := <-lw.Err: 514 t.Errorf("unexpected receive on lw.Err: err=%v, open=%v", err, open) 515 return nil 516 case msg, open := <-lw.Msg: 517 if !open { 518 select { 519 case err, open := <-lw.Err: 520 t.Errorf("unexpected receive on lw.Err with closed lw.Msg: err=%v, open=%v", err, open) 521 return nil 522 default: 523 } 524 } 525 if msg != nil { 526 t.Logf("loggertest: ReadMessage [%v %v] %s", msg.Source, msg.Timestamp, msg.Line) 527 } 528 return msg 529 } 530 } 531 532 func readAll(t *testing.T, lw *logger.LogWatcher) []*logger.Message { 533 t.Helper() 534 var msgs []*logger.Message 535 for { 536 m := readMessage(t, lw) 537 if m == nil { 538 return msgs 539 } 540 msgs = append(msgs, m) 541 } 542 }