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  }