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