github.com/kaisenlinux/docker@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/logger/loggerutils/follow.go (about)

     1  package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils"
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/docker/docker/daemon/logger"
     9  	"github.com/docker/docker/pkg/filenotify"
    10  	"github.com/fsnotify/fsnotify"
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  var errRetry = errors.New("retry")
    16  var errDone = errors.New("done")
    17  
    18  type follow struct {
    19  	file                      *os.File
    20  	dec                       Decoder
    21  	fileWatcher               filenotify.FileWatcher
    22  	logWatcher                *logger.LogWatcher
    23  	notifyRotate, notifyEvict chan interface{}
    24  	oldSize                   int64
    25  	retries                   int
    26  }
    27  
    28  func (fl *follow) handleRotate() error {
    29  	name := fl.file.Name()
    30  
    31  	fl.file.Close()
    32  	fl.fileWatcher.Remove(name)
    33  
    34  	// retry when the file doesn't exist
    35  	var err error
    36  	for retries := 0; retries <= 5; retries++ {
    37  		f, err := open(name)
    38  		if err == nil || !os.IsNotExist(err) {
    39  			fl.file = f
    40  			break
    41  		}
    42  	}
    43  	if err != nil {
    44  		return err
    45  	}
    46  	if err := fl.fileWatcher.Add(name); err != nil {
    47  		return err
    48  	}
    49  	fl.dec.Reset(fl.file)
    50  	return nil
    51  }
    52  
    53  func (fl *follow) handleMustClose(evictErr error) {
    54  	fl.file.Close()
    55  	fl.dec.Close()
    56  	fl.logWatcher.Err <- errors.Wrap(evictErr, "log reader evicted due to errors")
    57  	logrus.WithField("file", fl.file.Name()).Error("Log reader notified that it must re-open log file, some log data may not be streamed to the client.")
    58  }
    59  
    60  func (fl *follow) waitRead() error {
    61  	select {
    62  	case e := <-fl.notifyEvict:
    63  		if e != nil {
    64  			err := e.(error)
    65  			fl.handleMustClose(err)
    66  		}
    67  		return errDone
    68  	case e := <-fl.fileWatcher.Events():
    69  		switch e.Op {
    70  		case fsnotify.Write:
    71  			fl.dec.Reset(fl.file)
    72  			return nil
    73  		case fsnotify.Rename, fsnotify.Remove:
    74  			select {
    75  			case <-fl.notifyRotate:
    76  			case <-fl.logWatcher.WatchProducerGone():
    77  				return errDone
    78  			case <-fl.logWatcher.WatchConsumerGone():
    79  				return errDone
    80  			}
    81  			if err := fl.handleRotate(); err != nil {
    82  				return err
    83  			}
    84  			return nil
    85  		}
    86  		return errRetry
    87  	case err := <-fl.fileWatcher.Errors():
    88  		logrus.Debugf("logger got error watching file: %v", err)
    89  		// Something happened, let's try and stay alive and create a new watcher
    90  		if fl.retries <= 5 {
    91  			fl.fileWatcher.Close()
    92  			fl.fileWatcher, err = watchFile(fl.file.Name())
    93  			if err != nil {
    94  				return err
    95  			}
    96  			fl.retries++
    97  			return errRetry
    98  		}
    99  		return err
   100  	case <-fl.logWatcher.WatchProducerGone():
   101  		return errDone
   102  	case <-fl.logWatcher.WatchConsumerGone():
   103  		return errDone
   104  	}
   105  }
   106  
   107  func (fl *follow) handleDecodeErr(err error) error {
   108  	if !errors.Is(err, io.EOF) {
   109  		return err
   110  	}
   111  
   112  	// Handle special case (#39235): max-file=1 and file was truncated
   113  	st, stErr := fl.file.Stat()
   114  	if stErr == nil {
   115  		size := st.Size()
   116  		defer func() { fl.oldSize = size }()
   117  		if size < fl.oldSize { // truncated
   118  			fl.file.Seek(0, 0)
   119  			fl.dec.Reset(fl.file)
   120  			return nil
   121  		}
   122  	} else {
   123  		logrus.WithError(stErr).Warn("logger: stat error")
   124  	}
   125  
   126  	for {
   127  		err := fl.waitRead()
   128  		if err == nil {
   129  			break
   130  		}
   131  		if err == errRetry {
   132  			continue
   133  		}
   134  		return err
   135  	}
   136  	return nil
   137  }
   138  
   139  func (fl *follow) mainLoop(since, until time.Time) {
   140  	for {
   141  		select {
   142  		case err := <-fl.notifyEvict:
   143  			if err != nil {
   144  				fl.handleMustClose(err.(error))
   145  			}
   146  			return
   147  		default:
   148  		}
   149  		msg, err := fl.dec.Decode()
   150  		if err != nil {
   151  			if err := fl.handleDecodeErr(err); err != nil {
   152  				if err == errDone {
   153  					return
   154  				}
   155  				// we got an unrecoverable error, so return
   156  				fl.logWatcher.Err <- err
   157  				return
   158  			}
   159  			// ready to try again
   160  			continue
   161  		}
   162  
   163  		fl.retries = 0 // reset retries since we've succeeded
   164  		if !since.IsZero() && msg.Timestamp.Before(since) {
   165  			continue
   166  		}
   167  		if !until.IsZero() && msg.Timestamp.After(until) {
   168  			return
   169  		}
   170  		// send the message, unless the consumer is gone
   171  		select {
   172  		case e := <-fl.notifyEvict:
   173  			if e != nil {
   174  				err := e.(error)
   175  				logrus.WithError(err).Debug("Reader evicted while sending log message")
   176  				fl.logWatcher.Err <- err
   177  			}
   178  			return
   179  		case fl.logWatcher.Msg <- msg:
   180  		case <-fl.logWatcher.WatchConsumerGone():
   181  			return
   182  		}
   183  	}
   184  }
   185  
   186  func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate, notifyEvict chan interface{}, dec Decoder, since, until time.Time) {
   187  	dec.Reset(f)
   188  
   189  	name := f.Name()
   190  	fileWatcher, err := watchFile(name)
   191  	if err != nil {
   192  		logWatcher.Err <- err
   193  		return
   194  	}
   195  	defer func() {
   196  		f.Close()
   197  		dec.Close()
   198  		fileWatcher.Close()
   199  	}()
   200  
   201  	fl := &follow{
   202  		file:         f,
   203  		oldSize:      -1,
   204  		logWatcher:   logWatcher,
   205  		fileWatcher:  fileWatcher,
   206  		notifyRotate: notifyRotate,
   207  		notifyEvict:  notifyEvict,
   208  		dec:          dec,
   209  	}
   210  	fl.mainLoop(since, until)
   211  }