github.com/wozhu6104/docker@v20.10.10+incompatible/daemon/logger/loggerutils/logfile.go (about)

     1  package loggerutils // import "github.com/docker/docker/daemon/logger/loggerutils"
     2  
     3  import (
     4  	"compress/gzip"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/docker/docker/daemon/logger"
    17  	"github.com/docker/docker/pkg/filenotify"
    18  	"github.com/docker/docker/pkg/pools"
    19  	"github.com/docker/docker/pkg/pubsub"
    20  	"github.com/fsnotify/fsnotify"
    21  	"github.com/pkg/errors"
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  const tmpLogfileSuffix = ".tmp"
    26  
    27  // rotateFileMetadata is a metadata of the gzip header of the compressed log file
    28  type rotateFileMetadata struct {
    29  	LastTime time.Time `json:"lastTime,omitempty"`
    30  }
    31  
    32  // refCounter is a counter of logfile being referenced
    33  type refCounter struct {
    34  	mu      sync.Mutex
    35  	counter map[string]int
    36  }
    37  
    38  // Reference increase the reference counter for specified logfile
    39  func (rc *refCounter) GetReference(fileName string, openRefFile func(fileName string, exists bool) (*os.File, error)) (*os.File, error) {
    40  	rc.mu.Lock()
    41  	defer rc.mu.Unlock()
    42  
    43  	var (
    44  		file *os.File
    45  		err  error
    46  	)
    47  	_, ok := rc.counter[fileName]
    48  	file, err = openRefFile(fileName, ok)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	if ok {
    54  		rc.counter[fileName]++
    55  	} else if file != nil {
    56  		rc.counter[file.Name()] = 1
    57  	}
    58  
    59  	return file, nil
    60  }
    61  
    62  // Dereference reduce the reference counter for specified logfile
    63  func (rc *refCounter) Dereference(fileName string) error {
    64  	rc.mu.Lock()
    65  	defer rc.mu.Unlock()
    66  
    67  	rc.counter[fileName]--
    68  	if rc.counter[fileName] <= 0 {
    69  		delete(rc.counter, fileName)
    70  		err := os.Remove(fileName)
    71  		if err != nil && !os.IsNotExist(err) {
    72  			return err
    73  		}
    74  	}
    75  	return nil
    76  }
    77  
    78  // LogFile is Logger implementation for default Docker logging.
    79  type LogFile struct {
    80  	mu              sync.RWMutex // protects the logfile access
    81  	f               *os.File     // store for closing
    82  	closed          bool
    83  	rotateMu        sync.Mutex // blocks the next rotation until the current rotation is completed
    84  	capacity        int64      // maximum size of each file
    85  	currentSize     int64      // current size of the latest file
    86  	maxFiles        int        // maximum number of files
    87  	compress        bool       // whether old versions of log files are compressed
    88  	lastTimestamp   time.Time  // timestamp of the last log
    89  	filesRefCounter refCounter // keep reference-counted of decompressed files
    90  	notifyReaders   *pubsub.Publisher
    91  	marshal         logger.MarshalFunc
    92  	createDecoder   MakeDecoderFn
    93  	getTailReader   GetTailReaderFunc
    94  	perms           os.FileMode
    95  }
    96  
    97  // MakeDecoderFn creates a decoder
    98  type MakeDecoderFn func(rdr io.Reader) Decoder
    99  
   100  // Decoder is for reading logs
   101  // It is created by the log reader by calling the `MakeDecoderFunc`
   102  type Decoder interface {
   103  	// Reset resets the decoder
   104  	// Reset is called for certain events, such as log rotations
   105  	Reset(io.Reader)
   106  	// Decode decodes the next log messeage from the stream
   107  	Decode() (*logger.Message, error)
   108  	// Close signals to the decoder that it can release whatever resources it was using.
   109  	Close()
   110  }
   111  
   112  // SizeReaderAt defines a ReaderAt that also reports its size.
   113  // This is used for tailing log files.
   114  type SizeReaderAt interface {
   115  	io.ReaderAt
   116  	Size() int64
   117  }
   118  
   119  // GetTailReaderFunc is used to truncate a reader to only read as much as is required
   120  // in order to get the passed in number of log lines.
   121  // It returns the sectioned reader, the number of lines that the section reader
   122  // contains, and any error that occurs.
   123  type GetTailReaderFunc func(ctx context.Context, f SizeReaderAt, nLogLines int) (rdr io.Reader, nLines int, err error)
   124  
   125  // NewLogFile creates new LogFile
   126  func NewLogFile(logPath string, capacity int64, maxFiles int, compress bool, marshaller logger.MarshalFunc, decodeFunc MakeDecoderFn, perms os.FileMode, getTailReader GetTailReaderFunc) (*LogFile, error) {
   127  	log, err := openFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, perms)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	size, err := log.Seek(0, io.SeekEnd)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	return &LogFile{
   138  		f:               log,
   139  		capacity:        capacity,
   140  		currentSize:     size,
   141  		maxFiles:        maxFiles,
   142  		compress:        compress,
   143  		filesRefCounter: refCounter{counter: make(map[string]int)},
   144  		notifyReaders:   pubsub.NewPublisher(0, 1),
   145  		marshal:         marshaller,
   146  		createDecoder:   decodeFunc,
   147  		perms:           perms,
   148  		getTailReader:   getTailReader,
   149  	}, nil
   150  }
   151  
   152  // WriteLogEntry writes the provided log message to the current log file.
   153  // This may trigger a rotation event if the max file/capacity limits are hit.
   154  func (w *LogFile) WriteLogEntry(msg *logger.Message) error {
   155  	b, err := w.marshal(msg)
   156  	if err != nil {
   157  		return errors.Wrap(err, "error marshalling log message")
   158  	}
   159  
   160  	logger.PutMessage(msg)
   161  
   162  	w.mu.Lock()
   163  	if w.closed {
   164  		w.mu.Unlock()
   165  		return errors.New("cannot write because the output file was closed")
   166  	}
   167  
   168  	if err := w.checkCapacityAndRotate(); err != nil {
   169  		w.mu.Unlock()
   170  		return errors.Wrap(err, "error rotating log file")
   171  	}
   172  
   173  	n, err := w.f.Write(b)
   174  	if err == nil {
   175  		w.currentSize += int64(n)
   176  		w.lastTimestamp = msg.Timestamp
   177  	}
   178  
   179  	w.mu.Unlock()
   180  	return errors.Wrap(err, "error writing log entry")
   181  }
   182  
   183  func (w *LogFile) checkCapacityAndRotate() (retErr error) {
   184  	if w.capacity == -1 {
   185  		return nil
   186  	}
   187  	if w.currentSize < w.capacity {
   188  		return nil
   189  	}
   190  
   191  	w.rotateMu.Lock()
   192  	noCompress := w.maxFiles <= 1 || !w.compress
   193  	defer func() {
   194  		// If we aren't going to run the goroutine to compress the log file, then we need to unlock in this function.
   195  		// Otherwise the lock will be released in the goroutine that handles compression.
   196  		if retErr != nil || noCompress {
   197  			w.rotateMu.Unlock()
   198  		}
   199  	}()
   200  
   201  	fname := w.f.Name()
   202  	if err := w.f.Close(); err != nil {
   203  		// if there was an error during a prior rotate, the file could already be closed
   204  		if !errors.Is(err, os.ErrClosed) {
   205  			return errors.Wrap(err, "error closing file")
   206  		}
   207  	}
   208  
   209  	if err := rotate(fname, w.maxFiles, w.compress); err != nil {
   210  		logrus.WithError(err).Warn("Error rotating log file, log data may have been lost")
   211  	} else {
   212  		var renameErr error
   213  		for i := 0; i < 10; i++ {
   214  			if renameErr = os.Rename(fname, fname+".1"); renameErr != nil && !os.IsNotExist(renameErr) {
   215  				logrus.WithError(renameErr).WithField("file", fname).Debug("Error rotating current container log file, evicting readers and retrying")
   216  				w.notifyReaders.Publish(renameErr)
   217  				time.Sleep(100 * time.Millisecond)
   218  				continue
   219  			}
   220  			break
   221  		}
   222  		if renameErr != nil {
   223  			logrus.WithError(renameErr).Error("Error renaming current log file")
   224  		}
   225  	}
   226  
   227  	file, err := openFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, w.perms)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	w.f = file
   232  	w.currentSize = 0
   233  
   234  	w.notifyReaders.Publish(struct{}{})
   235  
   236  	if noCompress {
   237  		return nil
   238  	}
   239  
   240  	ts := w.lastTimestamp
   241  
   242  	go func() {
   243  		if err := compressFile(fname+".1", ts); err != nil {
   244  			logrus.WithError(err).Error("Error compressing log file after rotation")
   245  		}
   246  		w.rotateMu.Unlock()
   247  	}()
   248  
   249  	return nil
   250  }
   251  
   252  func rotate(name string, maxFiles int, compress bool) error {
   253  	if maxFiles < 2 {
   254  		return nil
   255  	}
   256  
   257  	var extension string
   258  	if compress {
   259  		extension = ".gz"
   260  	}
   261  
   262  	lastFile := fmt.Sprintf("%s.%d%s", name, maxFiles-1, extension)
   263  	err := os.Remove(lastFile)
   264  	if err != nil && !os.IsNotExist(err) {
   265  		return errors.Wrap(err, "error removing oldest log file")
   266  	}
   267  
   268  	for i := maxFiles - 1; i > 1; i-- {
   269  		toPath := name + "." + strconv.Itoa(i) + extension
   270  		fromPath := name + "." + strconv.Itoa(i-1) + extension
   271  		logrus.WithField("source", fromPath).WithField("target", toPath).Trace("Rotating log file")
   272  		if err := os.Rename(fromPath, toPath); err != nil && !os.IsNotExist(err) {
   273  			return err
   274  		}
   275  	}
   276  
   277  	return nil
   278  }
   279  
   280  func compressFile(fileName string, lastTimestamp time.Time) (retErr error) {
   281  	file, err := open(fileName)
   282  	if err != nil {
   283  		if os.IsNotExist(err) {
   284  			logrus.WithField("file", fileName).WithError(err).Debug("Could not open log file to compress")
   285  			return nil
   286  		}
   287  		return errors.Wrap(err, "failed to open log file")
   288  	}
   289  	defer func() {
   290  		file.Close()
   291  		if retErr == nil {
   292  			err := os.Remove(fileName)
   293  			if err != nil && !os.IsNotExist(err) {
   294  				retErr = errors.Wrap(err, "failed to remove source log file")
   295  			}
   296  		}
   297  	}()
   298  
   299  	outFile, err := openFile(fileName+".gz", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0640)
   300  	if err != nil {
   301  		return errors.Wrap(err, "failed to open or create gzip log file")
   302  	}
   303  	defer func() {
   304  		outFile.Close()
   305  		if retErr != nil {
   306  			if err := os.Remove(fileName + ".gz"); err != nil && !os.IsExist(err) {
   307  				logrus.WithError(err).Error("Error cleaning up after failed log compression")
   308  			}
   309  		}
   310  	}()
   311  
   312  	compressWriter := gzip.NewWriter(outFile)
   313  	defer compressWriter.Close()
   314  
   315  	// Add the last log entry timestamp to the gzip header
   316  	extra := rotateFileMetadata{}
   317  	extra.LastTime = lastTimestamp
   318  	compressWriter.Header.Extra, err = json.Marshal(&extra)
   319  	if err != nil {
   320  		// Here log the error only and don't return since this is just an optimization.
   321  		logrus.Warningf("Failed to marshal gzip header as JSON: %v", err)
   322  	}
   323  
   324  	_, err = pools.Copy(compressWriter, file)
   325  	if err != nil {
   326  		return errors.Wrapf(err, "error compressing log file %s", fileName)
   327  	}
   328  
   329  	return nil
   330  }
   331  
   332  // MaxFiles return maximum number of files
   333  func (w *LogFile) MaxFiles() int {
   334  	return w.maxFiles
   335  }
   336  
   337  // Close closes underlying file and signals all readers to stop.
   338  func (w *LogFile) Close() error {
   339  	w.mu.Lock()
   340  	defer w.mu.Unlock()
   341  	if w.closed {
   342  		return nil
   343  	}
   344  	if err := w.f.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
   345  		return err
   346  	}
   347  	w.closed = true
   348  	return nil
   349  }
   350  
   351  // ReadLogs decodes entries from log files and sends them the passed in watcher
   352  //
   353  // Note: Using the follow option can become inconsistent in cases with very frequent rotations and max log files is 1.
   354  // TODO: Consider a different implementation which can effectively follow logs under frequent rotations.
   355  func (w *LogFile) ReadLogs(config logger.ReadConfig, watcher *logger.LogWatcher) {
   356  	w.mu.RLock()
   357  	currentFile, err := open(w.f.Name())
   358  	if err != nil {
   359  		w.mu.RUnlock()
   360  		watcher.Err <- err
   361  		return
   362  	}
   363  	defer currentFile.Close()
   364  
   365  	dec := w.createDecoder(nil)
   366  	defer dec.Close()
   367  
   368  	currentChunk, err := newSectionReader(currentFile)
   369  	if err != nil {
   370  		w.mu.RUnlock()
   371  		watcher.Err <- err
   372  		return
   373  	}
   374  
   375  	notifyEvict := w.notifyReaders.SubscribeTopicWithBuffer(func(i interface{}) bool {
   376  		_, ok := i.(error)
   377  		return ok
   378  	}, 1)
   379  	defer w.notifyReaders.Evict(notifyEvict)
   380  
   381  	if config.Tail != 0 {
   382  		// TODO(@cpuguy83): Instead of opening every file, only get the files which
   383  		// are needed to tail.
   384  		// This is especially costly when compression is enabled.
   385  		files, err := w.openRotatedFiles(config)
   386  		w.mu.RUnlock()
   387  		if err != nil {
   388  			watcher.Err <- err
   389  			return
   390  		}
   391  
   392  		closeFiles := func() {
   393  			for _, f := range files {
   394  				f.Close()
   395  				fileName := f.Name()
   396  				if strings.HasSuffix(fileName, tmpLogfileSuffix) {
   397  					err := w.filesRefCounter.Dereference(fileName)
   398  					if err != nil {
   399  						logrus.WithError(err).WithField("file", fileName).Error("Failed to dereference the log file")
   400  					}
   401  				}
   402  			}
   403  		}
   404  
   405  		readers := make([]SizeReaderAt, 0, len(files)+1)
   406  		for _, f := range files {
   407  			stat, err := f.Stat()
   408  			if err != nil {
   409  				watcher.Err <- errors.Wrap(err, "error reading size of rotated file")
   410  				closeFiles()
   411  				return
   412  			}
   413  			readers = append(readers, io.NewSectionReader(f, 0, stat.Size()))
   414  		}
   415  		if currentChunk.Size() > 0 {
   416  			readers = append(readers, currentChunk)
   417  		}
   418  
   419  		ok := tailFiles(readers, watcher, dec, w.getTailReader, config, notifyEvict)
   420  		closeFiles()
   421  		if !ok {
   422  			return
   423  		}
   424  		w.mu.RLock()
   425  	}
   426  
   427  	if !config.Follow || w.closed {
   428  		w.mu.RUnlock()
   429  		return
   430  	}
   431  	w.mu.RUnlock()
   432  
   433  	notifyRotate := w.notifyReaders.SubscribeTopic(func(i interface{}) bool {
   434  		_, ok := i.(struct{})
   435  		return ok
   436  	})
   437  	defer w.notifyReaders.Evict(notifyRotate)
   438  
   439  	followLogs(currentFile, watcher, notifyRotate, notifyEvict, dec, config.Since, config.Until)
   440  }
   441  
   442  func (w *LogFile) openRotatedFiles(config logger.ReadConfig) (files []*os.File, err error) {
   443  	w.rotateMu.Lock()
   444  	defer w.rotateMu.Unlock()
   445  
   446  	defer func() {
   447  		if err == nil {
   448  			return
   449  		}
   450  		for _, f := range files {
   451  			f.Close()
   452  			if strings.HasSuffix(f.Name(), tmpLogfileSuffix) {
   453  				err := os.Remove(f.Name())
   454  				if err != nil && !os.IsNotExist(err) {
   455  					logrus.Warnf("Failed to remove logfile: %v", err)
   456  				}
   457  			}
   458  		}
   459  	}()
   460  
   461  	for i := w.maxFiles; i > 1; i-- {
   462  		f, err := open(fmt.Sprintf("%s.%d", w.f.Name(), i-1))
   463  		if err != nil {
   464  			if !os.IsNotExist(err) {
   465  				return nil, errors.Wrap(err, "error opening rotated log file")
   466  			}
   467  
   468  			fileName := fmt.Sprintf("%s.%d.gz", w.f.Name(), i-1)
   469  			decompressedFileName := fileName + tmpLogfileSuffix
   470  			tmpFile, err := w.filesRefCounter.GetReference(decompressedFileName, func(refFileName string, exists bool) (*os.File, error) {
   471  				if exists {
   472  					return open(refFileName)
   473  				}
   474  				return decompressfile(fileName, refFileName, config.Since)
   475  			})
   476  
   477  			if err != nil {
   478  				if !errors.Is(err, os.ErrNotExist) {
   479  					return nil, errors.Wrap(err, "error getting reference to decompressed log file")
   480  				}
   481  				continue
   482  			}
   483  			if tmpFile == nil {
   484  				// The log before `config.Since` does not need to read
   485  				break
   486  			}
   487  
   488  			files = append(files, tmpFile)
   489  			continue
   490  		}
   491  		files = append(files, f)
   492  	}
   493  
   494  	return files, nil
   495  }
   496  
   497  func decompressfile(fileName, destFileName string, since time.Time) (*os.File, error) {
   498  	cf, err := open(fileName)
   499  	if err != nil {
   500  		return nil, errors.Wrap(err, "error opening file for decompression")
   501  	}
   502  	defer cf.Close()
   503  
   504  	rc, err := gzip.NewReader(cf)
   505  	if err != nil {
   506  		return nil, errors.Wrap(err, "error making gzip reader for compressed log file")
   507  	}
   508  	defer rc.Close()
   509  
   510  	// Extract the last log entry timestramp from the gzip header
   511  	extra := &rotateFileMetadata{}
   512  	err = json.Unmarshal(rc.Header.Extra, extra)
   513  	if err == nil && extra.LastTime.Before(since) {
   514  		return nil, nil
   515  	}
   516  
   517  	rs, err := openFile(destFileName, os.O_CREATE|os.O_RDWR, 0640)
   518  	if err != nil {
   519  		return nil, errors.Wrap(err, "error creating file for copying decompressed log stream")
   520  	}
   521  
   522  	_, err = pools.Copy(rs, rc)
   523  	if err != nil {
   524  		rs.Close()
   525  		rErr := os.Remove(rs.Name())
   526  		if rErr != nil && !os.IsNotExist(rErr) {
   527  			logrus.Errorf("Failed to remove logfile: %v", rErr)
   528  		}
   529  		return nil, errors.Wrap(err, "error while copying decompressed log stream to file")
   530  	}
   531  
   532  	return rs, nil
   533  }
   534  
   535  func newSectionReader(f *os.File) (*io.SectionReader, error) {
   536  	// seek to the end to get the size
   537  	// we'll leave this at the end of the file since section reader does not advance the reader
   538  	size, err := f.Seek(0, io.SeekEnd)
   539  	if err != nil {
   540  		return nil, errors.Wrap(err, "error getting current file size")
   541  	}
   542  	return io.NewSectionReader(f, 0, size), nil
   543  }
   544  
   545  func tailFiles(files []SizeReaderAt, watcher *logger.LogWatcher, dec Decoder, getTailReader GetTailReaderFunc, config logger.ReadConfig, notifyEvict <-chan interface{}) (cont bool) {
   546  	nLines := config.Tail
   547  
   548  	ctx, cancel := context.WithCancel(context.Background())
   549  	defer cancel()
   550  
   551  	cont = true
   552  	// TODO(@cpuguy83): we should plumb a context through instead of dealing with `WatchClose()` here.
   553  	go func() {
   554  		select {
   555  		case err := <-notifyEvict:
   556  			if err != nil {
   557  				watcher.Err <- err.(error)
   558  				cont = false
   559  				cancel()
   560  			}
   561  		case <-ctx.Done():
   562  		case <-watcher.WatchConsumerGone():
   563  			cont = false
   564  			cancel()
   565  		}
   566  	}()
   567  
   568  	readers := make([]io.Reader, 0, len(files))
   569  
   570  	if config.Tail > 0 {
   571  		for i := len(files) - 1; i >= 0 && nLines > 0; i-- {
   572  			tail, n, err := getTailReader(ctx, files[i], nLines)
   573  			if err != nil {
   574  				watcher.Err <- errors.Wrap(err, "error finding file position to start log tailing")
   575  				return
   576  			}
   577  			nLines -= n
   578  			readers = append([]io.Reader{tail}, readers...)
   579  		}
   580  	} else {
   581  		for _, r := range files {
   582  			readers = append(readers, &wrappedReaderAt{ReaderAt: r})
   583  		}
   584  	}
   585  
   586  	rdr := io.MultiReader(readers...)
   587  	dec.Reset(rdr)
   588  
   589  	for {
   590  		msg, err := dec.Decode()
   591  		if err != nil {
   592  			if !errors.Is(err, io.EOF) {
   593  				watcher.Err <- err
   594  			}
   595  			return
   596  		}
   597  		if !config.Since.IsZero() && msg.Timestamp.Before(config.Since) {
   598  			continue
   599  		}
   600  		if !config.Until.IsZero() && msg.Timestamp.After(config.Until) {
   601  			return
   602  		}
   603  		select {
   604  		case <-ctx.Done():
   605  			return
   606  		case watcher.Msg <- msg:
   607  		}
   608  	}
   609  }
   610  
   611  func followLogs(f *os.File, logWatcher *logger.LogWatcher, notifyRotate, notifyEvict chan interface{}, dec Decoder, since, until time.Time) {
   612  	dec.Reset(f)
   613  
   614  	name := f.Name()
   615  	fileWatcher, err := watchFile(name)
   616  	if err != nil {
   617  		logWatcher.Err <- err
   618  		return
   619  	}
   620  	defer func() {
   621  		f.Close()
   622  		dec.Close()
   623  		fileWatcher.Close()
   624  	}()
   625  
   626  	var retries int
   627  	handleRotate := func() error {
   628  		f.Close()
   629  		fileWatcher.Remove(name)
   630  
   631  		// retry when the file doesn't exist
   632  		for retries := 0; retries <= 5; retries++ {
   633  			f, err = open(name)
   634  			if err == nil || !os.IsNotExist(err) {
   635  				break
   636  			}
   637  		}
   638  		if err != nil {
   639  			return err
   640  		}
   641  		if err := fileWatcher.Add(name); err != nil {
   642  			return err
   643  		}
   644  		dec.Reset(f)
   645  		return nil
   646  	}
   647  
   648  	errRetry := errors.New("retry")
   649  	errDone := errors.New("done")
   650  
   651  	handleMustClose := func(evictErr error) {
   652  		f.Close()
   653  		dec.Close()
   654  		logWatcher.Err <- errors.Wrap(err, "log reader evicted due to errors")
   655  		logrus.WithField("file", f.Name()).Error("Log reader notified that it must re-open log file, some log data may not be streamed to the client.")
   656  	}
   657  
   658  	waitRead := func() error {
   659  		select {
   660  		case e := <-notifyEvict:
   661  			if e != nil {
   662  				err := e.(error)
   663  				handleMustClose(err)
   664  			}
   665  			return errDone
   666  		case e := <-fileWatcher.Events():
   667  			switch e.Op {
   668  			case fsnotify.Write:
   669  				dec.Reset(f)
   670  				return nil
   671  			case fsnotify.Rename, fsnotify.Remove:
   672  				select {
   673  				case <-notifyRotate:
   674  				case <-logWatcher.WatchProducerGone():
   675  					return errDone
   676  				case <-logWatcher.WatchConsumerGone():
   677  					return errDone
   678  				}
   679  				if err := handleRotate(); err != nil {
   680  					return err
   681  				}
   682  				return nil
   683  			}
   684  			return errRetry
   685  		case err := <-fileWatcher.Errors():
   686  			logrus.Debugf("logger got error watching file: %v", err)
   687  			// Something happened, let's try and stay alive and create a new watcher
   688  			if retries <= 5 {
   689  				fileWatcher.Close()
   690  				fileWatcher, err = watchFile(name)
   691  				if err != nil {
   692  					return err
   693  				}
   694  				retries++
   695  				return errRetry
   696  			}
   697  			return err
   698  		case <-logWatcher.WatchProducerGone():
   699  			return errDone
   700  		case <-logWatcher.WatchConsumerGone():
   701  			return errDone
   702  		}
   703  	}
   704  
   705  	oldSize := int64(-1)
   706  	handleDecodeErr := func(err error) error {
   707  		if !errors.Is(err, io.EOF) {
   708  			return err
   709  		}
   710  
   711  		// Handle special case (#39235): max-file=1 and file was truncated
   712  		st, stErr := f.Stat()
   713  		if stErr == nil {
   714  			size := st.Size()
   715  			defer func() { oldSize = size }()
   716  			if size < oldSize { // truncated
   717  				f.Seek(0, 0)
   718  				return nil
   719  			}
   720  		} else {
   721  			logrus.WithError(stErr).Warn("logger: stat error")
   722  		}
   723  
   724  		for {
   725  			err := waitRead()
   726  			if err == nil {
   727  				break
   728  			}
   729  			if err == errRetry {
   730  				continue
   731  			}
   732  			return err
   733  		}
   734  		return nil
   735  	}
   736  
   737  	// main loop
   738  	for {
   739  		select {
   740  		case err := <-notifyEvict:
   741  			if err != nil {
   742  				handleMustClose(err.(error))
   743  			}
   744  			return
   745  		default:
   746  		}
   747  		msg, err := dec.Decode()
   748  		if err != nil {
   749  			if err := handleDecodeErr(err); err != nil {
   750  				if err == errDone {
   751  					return
   752  				}
   753  				// we got an unrecoverable error, so return
   754  				logWatcher.Err <- err
   755  				return
   756  			}
   757  			// ready to try again
   758  			continue
   759  		}
   760  
   761  		retries = 0 // reset retries since we've succeeded
   762  		if !since.IsZero() && msg.Timestamp.Before(since) {
   763  			continue
   764  		}
   765  		if !until.IsZero() && msg.Timestamp.After(until) {
   766  			return
   767  		}
   768  		// send the message, unless the consumer is gone
   769  		select {
   770  		case e := <-notifyEvict:
   771  			if e != nil {
   772  				err := e.(error)
   773  				logrus.WithError(err).Debug("Reader evicted while sending log message")
   774  				logWatcher.Err <- err
   775  			}
   776  			return
   777  		case logWatcher.Msg <- msg:
   778  		case <-logWatcher.WatchConsumerGone():
   779  			return
   780  		}
   781  	}
   782  }
   783  
   784  func watchFile(name string) (filenotify.FileWatcher, error) {
   785  	var fileWatcher filenotify.FileWatcher
   786  
   787  	if runtime.GOOS == "windows" {
   788  		// FileWatcher on Windows files is based on the syscall notifications which has an issue because of file caching.
   789  		// It is based on ReadDirectoryChangesW() which doesn't detect writes to the cache. It detects writes to disk only.
   790  		// Because of the OS lazy writing, we don't get notifications for file writes and thereby the watcher
   791  		// doesn't work. Hence for Windows we will use poll based notifier.
   792  		fileWatcher = filenotify.NewPollingWatcher()
   793  	} else {
   794  		var err error
   795  		fileWatcher, err = filenotify.New()
   796  		if err != nil {
   797  			return nil, err
   798  		}
   799  	}
   800  
   801  	logger := logrus.WithFields(logrus.Fields{
   802  		"module": "logger",
   803  		"file":   name,
   804  	})
   805  
   806  	if err := fileWatcher.Add(name); err != nil {
   807  		// we will retry using file poller.
   808  		logger.WithError(err).Warnf("falling back to file poller")
   809  		fileWatcher.Close()
   810  		fileWatcher = filenotify.NewPollingWatcher()
   811  
   812  		if err := fileWatcher.Add(name); err != nil {
   813  			fileWatcher.Close()
   814  			logger.WithError(err).Debugf("error watching log file for modifications")
   815  			return nil, err
   816  		}
   817  	}
   818  
   819  	return fileWatcher, nil
   820  }
   821  
   822  type wrappedReaderAt struct {
   823  	io.ReaderAt
   824  	pos int64
   825  }
   826  
   827  func (r *wrappedReaderAt) Read(p []byte) (int, error) {
   828  	n, err := r.ReaderAt.ReadAt(p, r.pos)
   829  	r.pos += int64(n)
   830  	return n, err
   831  }