github.com/mr-pmillz/tail@v1.5.0/tail.go (about)

     1  // Copyright (c) 2019 FOSS contributors of https://github.com/nxadm/tail
     2  // Copyright (c) 2015 HPE Software Inc. All rights reserved.
     3  // Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
     4  
     5  //nxadm/tail provides a Go library that emulates the features of the BSD `tail`
     6  //program. The library comes with full support for truncation/move detection as
     7  //it is designed to work with log rotation tools. The library works on all
     8  //operating systems supported by Go, including POSIX systems like Linux and
     9  //*BSD, and MS Windows. Go 1.9 is the oldest compiler release supported.
    10  package tail
    11  
    12  import (
    13  	"bufio"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"io/ioutil"
    18  	"log"
    19  	"os"
    20  	"strings"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/mr-pmillz/tail/ratelimiter"
    25  	"github.com/mr-pmillz/tail/util"
    26  	"github.com/mr-pmillz/tail/watch"
    27  	"gopkg.in/tomb.v1"
    28  )
    29  
    30  var (
    31  	// ErrStop is returned when the tail of a file has been marked to be stopped.
    32  	ErrStop = errors.New("tail should now stop")
    33  )
    34  
    35  type Line struct {
    36  	Text     string    // The contents of the file
    37  	Num      int       // The line number
    38  	SeekInfo SeekInfo  // SeekInfo
    39  	Time     time.Time // Present time
    40  	Err      error     // Error from tail
    41  }
    42  
    43  // Deprecated: this function is no longer used internally and it has little of no
    44  // use in the API. As such, it will be removed from the API in a future major
    45  // release.
    46  //
    47  // NewLine returns a * pointer to a Line struct.
    48  func NewLine(text string, lineNum int) *Line {
    49  	return &Line{text, lineNum, SeekInfo{}, time.Now(), nil}
    50  }
    51  
    52  // SeekInfo represents arguments to io.Seek. See: https://golang.org/pkg/io/#SectionReader.Seek
    53  type SeekInfo struct {
    54  	Offset int64
    55  	Whence int
    56  }
    57  
    58  type logger interface {
    59  	Fatal(v ...interface{})
    60  	Fatalf(format string, v ...interface{})
    61  	Fatalln(v ...interface{})
    62  	Panic(v ...interface{})
    63  	Panicf(format string, v ...interface{})
    64  	Panicln(v ...interface{})
    65  	Print(v ...interface{})
    66  	Printf(format string, v ...interface{})
    67  	Println(v ...interface{})
    68  }
    69  
    70  // Config is used to specify how a file must be tailed.
    71  type Config struct {
    72  	// File-specifc
    73  	Location  *SeekInfo // Tail from this location. If nil, start at the beginning of the file
    74  	ReOpen    bool      // Reopen recreated files (tail -F)
    75  	MustExist bool      // Fail early if the file does not exist
    76  	SyncOpen  bool      // Do any seek operation before returning from TailFile.
    77  	Poll      bool      // Poll for file changes instead of using the default inotify
    78  	Pipe      bool      // The file is a named pipe (mkfifo)
    79  
    80  	// Generic IO
    81  	Follow        bool // Continue looking for new lines (tail -f)
    82  	MaxLineSize   int  // If non-zero, split longer lines into multiple lines
    83  	CompleteLines bool // Only return complete lines (that end with "\n" or EOF when Follow is false)
    84  
    85  	// Optionally, use a ratelimiter (e.g. created by the ratelimiter/NewLeakyBucket function)
    86  	RateLimiter *ratelimiter.LeakyBucket
    87  
    88  	// Optionally use a Logger. When nil, the Logger is set to tail.DefaultLogger.
    89  	// To disable logging, set it to tail.DiscardingLogger
    90  	Logger logger
    91  }
    92  
    93  type Tail struct {
    94  	Filename string     // The filename
    95  	Lines    chan *Line // A consumable channel of *Line
    96  	Config              // Tail.Configuration
    97  
    98  	file    *os.File
    99  	reader  *bufio.Reader
   100  	lineNum int
   101  
   102  	lineBuf *strings.Builder
   103  
   104  	watcher watch.FileWatcher
   105  	changes *watch.FileChanges
   106  
   107  	tomb.Tomb // provides: Done, Kill, Dying
   108  
   109  	lk sync.Mutex
   110  }
   111  
   112  var (
   113  	// DefaultLogger logs to os.Stderr and it is used when Config.Logger == nil
   114  	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
   115  	// DiscardingLogger can be used to disable logging output
   116  	DiscardingLogger = log.New(ioutil.Discard, "", 0)
   117  )
   118  
   119  // TailFile begins tailing the file. And returns a pointer to a Tail struct
   120  // and an error. An output stream is made available via the Tail.Lines
   121  // channel (e.g. to be looped and printed). To handle errors during tailing,
   122  // after finishing reading from the Lines channel, invoke the `Wait` or `Err`
   123  // method on the returned *Tail.
   124  func TailFile(filename string, config Config) (*Tail, error) {
   125  	if config.ReOpen && !config.Follow {
   126  		util.Fatal("cannot set ReOpen without Follow.")
   127  	}
   128  
   129  	t := &Tail{
   130  		Filename: filename,
   131  		Lines:    make(chan *Line),
   132  		Config:   config,
   133  	}
   134  
   135  	if config.CompleteLines {
   136  		t.lineBuf = new(strings.Builder)
   137  	}
   138  
   139  	// when Logger was not specified in config, use default logger
   140  	if t.Logger == nil {
   141  		t.Logger = DefaultLogger
   142  	}
   143  
   144  	if t.Poll {
   145  		t.watcher = watch.NewPollingFileWatcher(filename)
   146  	} else {
   147  		t.watcher = watch.NewInotifyFileWatcher(filename)
   148  	}
   149  
   150  	if t.MustExist || t.SyncOpen {
   151  		var err error
   152  		t.file, err = OpenFile(t.Filename)
   153  		if t.MustExist {
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  		}
   158  		if t.SyncOpen && err == nil && t.Location != nil {
   159  			// Do any required seek operation before returning from TailFile.
   160  			//
   161  			// If the file didn't already exist, it's reasonable to assume that
   162  			// we shouldn't seek later when it opens, because its size when we
   163  			// first tried to open it was zero, so not seeking at all is fine.
   164  			_, err = t.file.Seek(t.Location.Offset, t.Location.Whence)
   165  			if err != nil {
   166  				return nil, err
   167  			}
   168  		}
   169  	}
   170  
   171  	go t.tailFileSync()
   172  
   173  	return t, nil
   174  }
   175  
   176  // Tell returns the file's current position, like stdio's ftell() and an error.
   177  // Beware that this value may not be completely accurate because one line from
   178  // the chan(tail.Lines) may have been read already.
   179  func (tail *Tail) Tell() (offset int64, err error) {
   180  	if tail.file == nil {
   181  		return
   182  	}
   183  	offset, err = tail.file.Seek(0, io.SeekCurrent)
   184  	if err != nil {
   185  		return
   186  	}
   187  
   188  	tail.lk.Lock()
   189  	defer tail.lk.Unlock()
   190  	if tail.reader == nil {
   191  		return
   192  	}
   193  
   194  	offset -= int64(tail.reader.Buffered())
   195  	return
   196  }
   197  
   198  // Stop stops the tailing activity.
   199  func (tail *Tail) Stop() error {
   200  	tail.Kill(nil)
   201  	return tail.Wait()
   202  }
   203  
   204  // StopAtEOF stops tailing as soon as the end of the file is reached. The function
   205  // returns an error,
   206  func (tail *Tail) StopAtEOF() error {
   207  	tail.Kill(errStopAtEOF)
   208  	return tail.Wait()
   209  }
   210  
   211  var errStopAtEOF = errors.New("tail: stop at eof")
   212  
   213  func (tail *Tail) close() {
   214  	close(tail.Lines)
   215  	tail.closeFile()
   216  }
   217  
   218  func (tail *Tail) closeFile() {
   219  	if tail.file != nil {
   220  		tail.file.Close()
   221  		tail.file = nil
   222  	}
   223  }
   224  
   225  func (tail *Tail) reopen() error {
   226  	if tail.lineBuf != nil {
   227  		tail.lineBuf.Reset()
   228  	}
   229  	tail.closeFile()
   230  	tail.lineNum = 0
   231  	for {
   232  		var err error
   233  		tail.file, err = OpenFile(tail.Filename)
   234  		if err != nil {
   235  			if os.IsNotExist(err) {
   236  				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
   237  				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
   238  					if err == tomb.ErrDying {
   239  						return err
   240  					}
   241  					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
   242  				}
   243  				continue
   244  			}
   245  			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
   246  		}
   247  		break
   248  	}
   249  	return nil
   250  }
   251  
   252  func (tail *Tail) readLine() (string, error) {
   253  	tail.lk.Lock()
   254  	line, err := tail.reader.ReadString('\n')
   255  	tail.lk.Unlock()
   256  
   257  	newlineEnding := strings.HasSuffix(line, "\n")
   258  	line = strings.TrimRight(line, "\n")
   259  
   260  	// if we don't have to handle incomplete lines, we can return the line as-is
   261  	if !tail.Config.CompleteLines {
   262  		// Note ReadString "returns the data read before the error" in
   263  		// case of an error, including EOF, so we return it as is. The
   264  		// caller is expected to process it if err is EOF.
   265  		return line, err
   266  	}
   267  
   268  	if _, err := tail.lineBuf.WriteString(line); err != nil {
   269  		return line, err
   270  	}
   271  
   272  	if newlineEnding {
   273  		line = tail.lineBuf.String()
   274  		tail.lineBuf.Reset()
   275  		return line, nil
   276  	} else {
   277  		if tail.Config.Follow {
   278  			line = ""
   279  		}
   280  		return line, io.EOF
   281  	}
   282  }
   283  
   284  func (tail *Tail) tailFileSync() {
   285  	defer tail.Done()
   286  	defer tail.close()
   287  
   288  	if !tail.MustExist {
   289  		// deferred first open.
   290  		err := tail.reopen()
   291  		if err != nil {
   292  			if err != tomb.ErrDying {
   293  				tail.Kill(err)
   294  			}
   295  			return
   296  		}
   297  	}
   298  
   299  	// Seek to requested location on first open of the file.
   300  	if tail.Location != nil && !tail.SyncOpen {
   301  		_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
   302  		if err != nil {
   303  			tail.Killf("Seek error on %s: %s", tail.Filename, err)
   304  			return
   305  		}
   306  	}
   307  
   308  	tail.openReader()
   309  
   310  	// Read line by line.
   311  	for {
   312  		// do not seek in named pipes
   313  		if !tail.Pipe {
   314  			// grab the position in case we need to back up in the event of a half-line
   315  			if _, err := tail.Tell(); err != nil {
   316  				tail.Kill(err)
   317  				return
   318  			}
   319  		}
   320  
   321  		line, err := tail.readLine()
   322  
   323  		// Process `line` even if err is EOF.
   324  		if err == nil {
   325  			cooloff := !tail.sendLine(line)
   326  			if cooloff {
   327  				// Wait a second before seeking till the end of
   328  				// file when rate limit is reached.
   329  				msg := ("Too much log activity; waiting a second before resuming tailing")
   330  				offset, _ := tail.Tell()
   331  				tail.Lines <- &Line{msg, tail.lineNum, SeekInfo{Offset: offset}, time.Now(), errors.New(msg)}
   332  				select {
   333  				case <-time.After(time.Second):
   334  				case <-tail.Dying():
   335  					return
   336  				}
   337  				if err := tail.seekEnd(); err != nil {
   338  					tail.Kill(err)
   339  					return
   340  				}
   341  			}
   342  		} else if err == io.EOF {
   343  			if !tail.Follow {
   344  				if line != "" {
   345  					tail.sendLine(line)
   346  				}
   347  				return
   348  			}
   349  
   350  			if tail.Follow && line != "" {
   351  				tail.sendLine(line)
   352  				if err := tail.seekEnd(); err != nil {
   353  					tail.Kill(err)
   354  					return
   355  				}
   356  			}
   357  
   358  			// When EOF is reached, wait for more data to become
   359  			// available. Wait strategy is based on the `tail.watcher`
   360  			// implementation (inotify or polling).
   361  			err := tail.waitForChanges()
   362  			if err != nil {
   363  				if err != ErrStop {
   364  					tail.Kill(err)
   365  				}
   366  				return
   367  			}
   368  		} else {
   369  			// non-EOF error
   370  			tail.Killf("Error reading %s: %s", tail.Filename, err)
   371  			return
   372  		}
   373  
   374  		select {
   375  		case <-tail.Dying():
   376  			if tail.Err() == errStopAtEOF {
   377  				continue
   378  			}
   379  			return
   380  		default:
   381  		}
   382  	}
   383  }
   384  
   385  // waitForChanges waits until the file has been appended, deleted,
   386  // moved or truncated. When moved or deleted - the file will be
   387  // reopened if ReOpen is true. Truncated files are always reopened.
   388  func (tail *Tail) waitForChanges() error {
   389  	if tail.changes == nil {
   390  		pos, err := tail.file.Seek(0, io.SeekCurrent)
   391  		if err != nil {
   392  			return err
   393  		}
   394  		tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
   395  		if err != nil {
   396  			return err
   397  		}
   398  	}
   399  
   400  	select {
   401  	case <-tail.changes.Modified:
   402  		return nil
   403  	case <-tail.changes.Deleted:
   404  		tail.changes = nil
   405  		if tail.ReOpen {
   406  			// XXX: we must not log from a library.
   407  			tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
   408  			if err := tail.reopen(); err != nil {
   409  				return err
   410  			}
   411  			tail.Logger.Printf("Successfully reopened %s", tail.Filename)
   412  			tail.openReader()
   413  			return nil
   414  		}
   415  		tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
   416  		return ErrStop
   417  	case <-tail.changes.Truncated:
   418  		// Always reopen truncated files (Follow is true)
   419  		tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
   420  		if err := tail.reopen(); err != nil {
   421  			return err
   422  		}
   423  		tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
   424  		tail.openReader()
   425  		return nil
   426  	case <-tail.Dying():
   427  		return ErrStop
   428  	}
   429  }
   430  
   431  func (tail *Tail) openReader() {
   432  	tail.lk.Lock()
   433  	if tail.MaxLineSize > 0 {
   434  		// add 2 to account for newline characters
   435  		tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
   436  	} else {
   437  		tail.reader = bufio.NewReader(tail.file)
   438  	}
   439  	tail.lk.Unlock()
   440  }
   441  
   442  func (tail *Tail) seekEnd() error {
   443  	return tail.seekTo(SeekInfo{Offset: 0, Whence: io.SeekEnd})
   444  }
   445  
   446  func (tail *Tail) seekTo(pos SeekInfo) error {
   447  	_, err := tail.file.Seek(pos.Offset, pos.Whence)
   448  	if err != nil {
   449  		return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
   450  	}
   451  	// Reset the read buffer whenever the file is re-seek'ed
   452  	tail.reader.Reset(tail.file)
   453  	return nil
   454  }
   455  
   456  // sendLine sends the line(s) to Lines channel, splitting longer lines
   457  // if necessary. Return false if rate limit is reached.
   458  func (tail *Tail) sendLine(line string) bool {
   459  	now := time.Now()
   460  	lines := []string{line}
   461  
   462  	// Split longer lines
   463  	if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
   464  		lines = util.PartitionString(line, tail.MaxLineSize)
   465  	}
   466  
   467  	for _, line := range lines {
   468  		tail.lineNum++
   469  		offset, _ := tail.Tell()
   470  		select {
   471  		case tail.Lines <- &Line{line, tail.lineNum, SeekInfo{Offset: offset}, now, nil}:
   472  		case <-tail.Dying():
   473  			return true
   474  		}
   475  	}
   476  
   477  	if tail.Config.RateLimiter != nil {
   478  		ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
   479  		if !ok {
   480  			tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.",
   481  				tail.Filename)
   482  			return false
   483  		}
   484  	}
   485  
   486  	return true
   487  }
   488  
   489  // Cleanup removes inotify watches added by the tail package. This function is
   490  // meant to be invoked from a process's exit handler. Linux kernel may not
   491  // automatically remove inotify watches after the process exits.
   492  // If you plan to re-read a file, don't call Cleanup in between.
   493  func (tail *Tail) Cleanup() {
   494  	watch.Cleanup(tail.Filename)
   495  }