github.com/admpub/tail@v1.1.0/tail.go (about)

     1  // Copyright (c) 2015 HPE Software Inc. All rights reserved.
     2  // Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
     3  
     4  package tail
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"log"
    14  	"os"
    15  	"strings"
    16  	"sync"
    17  	"time"
    18  
    19  	"github.com/admpub/tail/ratelimiter"
    20  	"github.com/admpub/tail/util"
    21  	"github.com/admpub/tail/watch"
    22  	tomb "gopkg.in/tomb.v1"
    23  )
    24  
    25  var (
    26  	ErrStop = errors.New("tail should now stop")
    27  )
    28  
    29  type Line struct {
    30  	Text string
    31  	Time time.Time
    32  	Err  error // Error from tail
    33  }
    34  
    35  // NewLine returns a Line with present time.
    36  func NewLine(text string) *Line {
    37  	return &Line{text, time.Now(), nil}
    38  }
    39  
    40  // SeekInfo represents arguments to `os.Seek`
    41  type SeekInfo struct {
    42  	Offset int64
    43  	Whence int // os.SEEK_*
    44  }
    45  
    46  type logger interface {
    47  	Fatal(v ...interface{})
    48  	Fatalf(format string, v ...interface{})
    49  	Fatalln(v ...interface{})
    50  	Panic(v ...interface{})
    51  	Panicf(format string, v ...interface{})
    52  	Panicln(v ...interface{})
    53  	Print(v ...interface{})
    54  	Printf(format string, v ...interface{})
    55  	Println(v ...interface{})
    56  }
    57  
    58  // Config is used to specify how a file must be tailed.
    59  type Config struct {
    60  	// File-specifc
    61  	Location    *SeekInfo // Seek to this location before tailing
    62  	ReOpen      bool      // Reopen recreated files (tail -F)
    63  	MustExist   bool      // Fail early if the file does not exist
    64  	Poll        bool      // Poll for file changes instead of using inotify
    65  	Pipe        bool      // Is a named pipe (mkfifo)
    66  	RateLimiter *ratelimiter.LeakyBucket
    67  
    68  	LastLines    int  // Output the last NUM lines (tail -n)
    69  	FromLine     int  // Output starting with line NUM (tail -n)
    70  	PageSize     int  // Buffer size for seek line. Default 4096
    71  	SeekOnReOpen bool // Start from line on reopen
    72  
    73  	// Generic IO
    74  	Follow      bool // Continue looking for new lines (tail -f)
    75  	MaxLineSize int  // If non-zero, split longer lines into multiple lines
    76  
    77  	// Logger, when nil, is set to tail.DefaultLogger
    78  	// To disable logging: set field to tail.DiscardingLogger
    79  	Logger logger
    80  }
    81  
    82  type Tail struct {
    83  	Filename string
    84  	Lines    chan *Line
    85  	Config
    86  
    87  	file   *os.File
    88  	reader *bufio.Reader
    89  
    90  	watcher watch.FileWatcher
    91  	changes *watch.FileChanges
    92  
    93  	tomb.Tomb // provides: Done, Kill, Dying
    94  
    95  	lk sync.Mutex
    96  }
    97  
    98  var (
    99  	// DefaultLogger is used when Config.Logger == nil
   100  	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
   101  	// DiscardingLogger can be used to disable logging output
   102  	DiscardingLogger = log.New(ioutil.Discard, "", 0)
   103  )
   104  
   105  // TailFile begins tailing the file. Output stream is made available
   106  // via the `Tail.Lines` channel. To handle errors during tailing,
   107  // invoke the `Wait` or `Err` method after finishing reading from the
   108  // `Lines` channel.
   109  func TailFile(filename string, config Config) (*Tail, error) {
   110  	if config.ReOpen && !config.Follow {
   111  		util.Fatal("cannot set ReOpen without Follow.")
   112  	}
   113  
   114  	t := &Tail{
   115  		Filename: filename,
   116  		Lines:    make(chan *Line),
   117  		Config:   config,
   118  	}
   119  
   120  	// when Logger was not specified in config, use default logger
   121  	if t.Logger == nil {
   122  		t.Logger = log.New(os.Stderr, "", log.LstdFlags)
   123  	}
   124  
   125  	if t.Poll {
   126  		t.watcher = watch.NewPollingFileWatcher(filename)
   127  	} else {
   128  		t.watcher = watch.NewInotifyFileWatcher(filename)
   129  	}
   130  
   131  	if t.MustExist {
   132  		var err error
   133  		t.file, err = OpenFile(t.Filename)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  	}
   138  
   139  	go t.tailFileSync()
   140  
   141  	return t, nil
   142  }
   143  
   144  // Update tail.Config.Location corresponding to tail.Config.LastLines.
   145  // It goes with step defined in PageSize or 4096 by default
   146  // Or set to 0 if tail.Config.LastLines more that lines in file
   147  func (tail *Tail) readLast() {
   148  
   149  	count := 0
   150  	nLines := tail.Config.LastLines
   151  
   152  	seek := &SeekInfo{
   153  		Offset: 0,
   154  		Whence: 2, // io.SeekEnd
   155  	}
   156  
   157  	pos, err := tail.file.Seek(seek.Offset, seek.Whence)
   158  
   159  	if err != nil && err != io.EOF {
   160  		fmt.Printf("Seek error on %s: %s\n", tail.Filename, err)
   161  	}
   162  
   163  	pageSize := 4096
   164  	if tail.Config.PageSize != 0 {
   165  		pageSize = tail.Config.PageSize
   166  	}
   167  
   168  	// Skip сlosing \n if exist
   169  	b1 := make([]byte, 1)
   170  	tail.file.ReadAt(b1, pos-1)
   171  
   172  	if '\n' == b1[0] {
   173  		pos = pos - 1
   174  	}
   175  
   176  	for {
   177  		readFrom := pos - int64(pageSize)
   178  		lines := make([]int64, 0)
   179  
   180  		if readFrom <= 0 {
   181  			pageSize += int(readFrom)
   182  			readFrom = 0
   183  			lines = append(lines, 0)
   184  			count++
   185  		}
   186  
   187  		b := make([]byte, pageSize)
   188  
   189  		_, err := tail.file.ReadAt(b, readFrom)
   190  
   191  		if err != nil && err != io.EOF {
   192  			fmt.Printf("Read error on %s: %s\n", tail.Filename, err)
   193  		}
   194  
   195  		i := 0
   196  
   197  		// Find newline symbols location in buffer and put it in to slice
   198  		for {
   199  			bufPos := bytes.IndexByte(b[i:], '\n')
   200  			if bufPos == -1 {
   201  				break
   202  			}
   203  			i = i + bufPos + 1
   204  
   205  			lines = append(lines, int64(i))
   206  			count++
   207  		}
   208  
   209  		var firstLinePos int64
   210  
   211  		// If no lines found in buf set firstLinePos to 0
   212  		// Its needed to handle lines bigger than PageSize
   213  		if len(lines) == 0 {
   214  			firstLinePos = 0
   215  		} else {
   216  			firstLinePos = lines[0]
   217  		}
   218  
   219  		if count == nLines {
   220  			tail.Config.Location = &SeekInfo{
   221  				Offset: firstLinePos + readFrom,
   222  				Whence: 0, // io.SeekStart
   223  			}
   224  			return
   225  		}
   226  
   227  		if count > nLines {
   228  			linesLeft := count - nLines
   229  			targetPos := lines[linesLeft]
   230  			tail.Config.Location = &SeekInfo{
   231  				Offset: targetPos + readFrom,
   232  				Whence: 0, // io.SeekStart
   233  			}
   234  			return
   235  		}
   236  
   237  		if readFrom == 0 {
   238  			tail.Config.Location = &SeekInfo{
   239  				Offset: 0,
   240  				Whence: 0, // io.SeekStart
   241  			}
   242  			return
   243  		}
   244  		pos = firstLinePos + readFrom - 1
   245  	}
   246  }
   247  
   248  // Update tail.Config.Location corresponding to tail.Config.FromLine
   249  // Or set to end of the file if FromLine  more that lines in file
   250  // it goes with step defined in PageSize or 4096 by default
   251  func (tail *Tail) skipLines() {
   252  
   253  	fileStat, err := tail.file.Stat()
   254  	if err != nil {
   255  		fmt.Println(err)
   256  	}
   257  	fileSize := fileStat.Size()
   258  
   259  	count := 1
   260  	nLines := tail.Config.FromLine
   261  
   262  	pageSize := 4096
   263  	if tail.Config.PageSize != 0 {
   264  		pageSize = tail.Config.PageSize
   265  	}
   266  
   267  	// Skip сlosing \n if exist
   268  	b1 := make([]byte, 1)
   269  	tail.file.ReadAt(b1, fileSize-1)
   270  
   271  	if '\n' == b1[0] {
   272  		fileSize = fileSize - 1
   273  	}
   274  
   275  	var pos int64
   276  	pos = 0
   277  
   278  	for {
   279  
   280  		b := make([]byte, pageSize)
   281  
   282  		_, err := tail.file.ReadAt(b, pos)
   283  		if err != nil && err != io.EOF {
   284  			fmt.Printf("Read error on %s: %s\n", tail.Filename, err)
   285  		}
   286  
   287  		i := 0
   288  
   289  		// Find newline symbols location in buffer and put it in to slice
   290  		for {
   291  			bufPos := bytes.IndexByte(b[i:], '\n')
   292  			if bufPos == -1 {
   293  				break
   294  			}
   295  
   296  			i = i + bufPos + 1
   297  
   298  			count++
   299  			if count == nLines {
   300  				tail.Config.Location = &SeekInfo{
   301  					Offset: int64(i) + pos,
   302  					Whence: 0, // io.SeekStart
   303  				}
   304  				return
   305  			}
   306  		}
   307  
   308  		pos = pos + int64(pageSize)
   309  
   310  		if pos >= fileSize {
   311  			tail.Config.Location = &SeekInfo{
   312  				Offset: 0,
   313  				Whence: 2, // io.SeekEnd
   314  			}
   315  			return
   316  		}
   317  	}
   318  }
   319  
   320  // Tell Return the file's current position, like stdio's ftell().
   321  // But this value is not very accurate.
   322  // it may readed one line in the chan(tail.Lines),
   323  // so it may lost one line.
   324  func (tail *Tail) Tell() (offset int64, err error) {
   325  	if tail.file == nil {
   326  		return
   327  	}
   328  	offset, err = tail.file.Seek(0, os.SEEK_CUR)
   329  	if err != nil {
   330  		return
   331  	}
   332  
   333  	tail.lk.Lock()
   334  	defer tail.lk.Unlock()
   335  	if tail.reader == nil {
   336  		return
   337  	}
   338  
   339  	offset -= int64(tail.reader.Buffered())
   340  	return
   341  }
   342  
   343  // Stop stops the tailing activity.
   344  func (tail *Tail) Stop() error {
   345  	tail.Kill(nil)
   346  	return tail.Wait()
   347  }
   348  
   349  // StopAtEOF stops tailing as soon as the end of the file is reached.
   350  func (tail *Tail) StopAtEOF() error {
   351  	tail.Kill(errStopAtEOF)
   352  	return tail.Wait()
   353  }
   354  
   355  var errStopAtEOF = errors.New("tail: stop at eof")
   356  
   357  func (tail *Tail) close() {
   358  	close(tail.Lines)
   359  	tail.closeFile()
   360  }
   361  
   362  func (tail *Tail) closeFile() {
   363  	if tail.file != nil {
   364  		tail.file.Close()
   365  		tail.file = nil
   366  	}
   367  }
   368  
   369  func (tail *Tail) reopen() error {
   370  	tail.closeFile()
   371  	for {
   372  		var err error
   373  		tail.file, err = OpenFile(tail.Filename)
   374  		if err != nil {
   375  			if os.IsNotExist(err) {
   376  				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
   377  				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
   378  					if err == tomb.ErrDying {
   379  						return err
   380  					}
   381  					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
   382  				}
   383  				continue
   384  			}
   385  			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
   386  		}
   387  		break
   388  	}
   389  	return nil
   390  }
   391  
   392  func (tail *Tail) readLine() (string, error) {
   393  	tail.lk.Lock()
   394  	line, err := tail.reader.ReadString('\n')
   395  	tail.lk.Unlock()
   396  	if err != nil {
   397  		// Note ReadString "returns the data read before the error" in
   398  		// case of an error, including EOF, so we return it as is. The
   399  		// caller is expected to process it if err is EOF.
   400  		return line, err
   401  	}
   402  
   403  	line = strings.TrimRight(line, "\n")
   404  
   405  	return line, err
   406  }
   407  
   408  func (tail *Tail) findLine() {
   409  
   410  	if tail.Config.FromLine > 0 {
   411  		tail.skipLines()
   412  	} else if tail.Config.LastLines > 0 {
   413  		tail.readLast()
   414  	}
   415  	// Seek to requested location on first open of the file.
   416  	if tail.Location != nil {
   417  		_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
   418  		//tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
   419  		if err != nil {
   420  			tail.Killf("Seek error on %s: %s", tail.Filename, err)
   421  			return
   422  		}
   423  	}
   424  }
   425  
   426  func (tail *Tail) tailFileSync() {
   427  	defer tail.Done()
   428  	defer tail.close()
   429  
   430  	if !tail.MustExist {
   431  		// deferred first open.
   432  		err := tail.reopen()
   433  		if err != nil {
   434  			if err != tomb.ErrDying {
   435  				tail.Kill(err)
   436  			}
   437  			return
   438  		}
   439  	}
   440  
   441  	tail.findLine()
   442  	tail.openReader()
   443  
   444  	var offset int64
   445  	var err error
   446  
   447  	// Read line by line.
   448  	for {
   449  		// do not seek in named pipes
   450  		if !tail.Pipe {
   451  			// grab the position in case we need to back up in the event of a half-line
   452  			offset, err = tail.Tell()
   453  			if err != nil {
   454  				tail.Kill(err)
   455  				return
   456  			}
   457  		}
   458  
   459  		line, err := tail.readLine()
   460  
   461  		// Process `line` even if err is EOF.
   462  		if err == nil {
   463  			cooloff := !tail.sendLine(line)
   464  			if cooloff {
   465  				// Wait a second before seeking till the end of
   466  				// file when rate limit is reached.
   467  				msg := ("Too much log activity; waiting a second " +
   468  					"before resuming tailing")
   469  				tail.Lines <- &Line{msg, time.Now(), errors.New(msg)}
   470  				timer := time.NewTimer(time.Second)
   471  				defer timer.Stop()
   472  				select {
   473  				case <-timer.C:
   474  				case <-tail.Dying():
   475  					return
   476  				}
   477  				if err := tail.seekEnd(); err != nil {
   478  					tail.Kill(err)
   479  					return
   480  				}
   481  			}
   482  		} else if err == io.EOF {
   483  			if !tail.Follow {
   484  				if line != "" {
   485  					tail.sendLine(line)
   486  				}
   487  				return
   488  			}
   489  
   490  			if tail.Follow && line != "" {
   491  				// this has the potential to never return the last line if
   492  				// it's not followed by a newline; seems a fair trade here
   493  				err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0})
   494  				if err != nil {
   495  					tail.Kill(err)
   496  					return
   497  				}
   498  			}
   499  
   500  			// When EOF is reached, wait for more data to become
   501  			// available. Wait strategy is based on the `tail.watcher`
   502  			// implementation (inotify or polling).
   503  			err := tail.waitForChanges()
   504  			if err != nil {
   505  				if err != ErrStop {
   506  					tail.Kill(err)
   507  				}
   508  				return
   509  			}
   510  		} else {
   511  			// non-EOF error
   512  			tail.Killf("Error reading %s: %s", tail.Filename, err)
   513  			return
   514  		}
   515  
   516  		select {
   517  		case <-tail.Dying():
   518  			if tail.Err() == errStopAtEOF {
   519  				continue
   520  			}
   521  			return
   522  		default:
   523  		}
   524  	}
   525  }
   526  
   527  // waitForChanges waits until the file has been appended, deleted,
   528  // moved or truncated. When moved or deleted - the file will be
   529  // reopened if ReOpen is true. Truncated files are always reopened.
   530  func (tail *Tail) waitForChanges() error {
   531  	if tail.changes == nil {
   532  		pos, err := tail.file.Seek(0, os.SEEK_CUR)
   533  		if err != nil {
   534  			return err
   535  		}
   536  		tail.changes, err = tail.watcher.ChangeEvents(&tail.Tomb, pos)
   537  		if err != nil {
   538  			return err
   539  		}
   540  	}
   541  
   542  	select {
   543  	case <-tail.changes.Modified:
   544  		return nil
   545  	case <-tail.changes.Deleted:
   546  		tail.changes = nil
   547  		if tail.ReOpen {
   548  			// XXX: we must not log from a library.
   549  			tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
   550  			if err := tail.reopen(); err != nil {
   551  				return err
   552  			}
   553  			tail.Logger.Printf("Successfully reopened %s", tail.Filename)
   554  
   555  			if tail.Config.SeekOnReOpen {
   556  				tail.findLine()
   557  			}
   558  			tail.openReader()
   559  			return nil
   560  		} else {
   561  			tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
   562  			return ErrStop
   563  		}
   564  	case <-tail.changes.Truncated:
   565  		// Always reopen truncated files (Follow is true)
   566  		tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
   567  		if err := tail.reopen(); err != nil {
   568  			return err
   569  		}
   570  		tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
   571  		tail.openReader()
   572  		return nil
   573  	case <-tail.Dying():
   574  		return ErrStop
   575  	}
   576  	panic("unreachable")
   577  }
   578  
   579  func (tail *Tail) openReader() {
   580  	if tail.MaxLineSize > 0 {
   581  		// add 2 to account for newline characters
   582  		tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
   583  	} else {
   584  		tail.reader = bufio.NewReader(tail.file)
   585  	}
   586  }
   587  
   588  func (tail *Tail) seekEnd() error {
   589  	return tail.seekTo(SeekInfo{Offset: 0, Whence: os.SEEK_END})
   590  }
   591  
   592  func (tail *Tail) seekTo(pos SeekInfo) error {
   593  	_, err := tail.file.Seek(pos.Offset, pos.Whence)
   594  	if err != nil {
   595  		return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
   596  	}
   597  	// Reset the read buffer whenever the file is re-seek'ed
   598  	tail.reader.Reset(tail.file)
   599  	return nil
   600  }
   601  
   602  // sendLine sends the line(s) to Lines channel, splitting longer lines
   603  // if necessary. Return false if rate limit is reached.
   604  func (tail *Tail) sendLine(line string) bool {
   605  	now := time.Now()
   606  	lines := []string{line}
   607  
   608  	// Split longer lines
   609  	if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
   610  		lines = util.PartitionString(line, tail.MaxLineSize)
   611  	}
   612  
   613  	for _, line := range lines {
   614  		tail.Lines <- &Line{line, now, nil}
   615  	}
   616  
   617  	if tail.Config.RateLimiter != nil {
   618  		ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
   619  		if !ok {
   620  			tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
   621  				tail.Filename)
   622  			return false
   623  		}
   624  	}
   625  
   626  	return true
   627  }
   628  
   629  // Cleanup removes inotify watches added by the tail package. This function is
   630  // meant to be invoked from a process's exit handler. Linux kernel may not
   631  // automatically remove inotify watches after the process exits.
   632  func (tail *Tail) Cleanup() {
   633  	watch.Cleanup(tail.Filename)
   634  }