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