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  }