2013-01-08 04:54:49 +08:00
|
|
|
// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.
|
|
|
|
|
2012-10-10 06:13:05 +08:00
|
|
|
package tail
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2012-10-14 02:44:47 +08:00
|
|
|
"fmt"
|
2014-04-30 07:39:57 +08:00
|
|
|
"github.com/ActiveState/tail/ratelimiter"
|
2013-10-12 09:19:43 +08:00
|
|
|
"github.com/ActiveState/tail/util"
|
2013-05-29 10:33:52 +08:00
|
|
|
"github.com/ActiveState/tail/watch"
|
2012-10-10 06:13:05 +08:00
|
|
|
"io"
|
2014-02-22 22:48:23 +08:00
|
|
|
"io/ioutil"
|
2012-10-13 07:28:22 +08:00
|
|
|
"launchpad.net/tomb"
|
2012-10-10 06:13:05 +08:00
|
|
|
"log"
|
|
|
|
"os"
|
2014-05-17 09:01:10 +08:00
|
|
|
"strings"
|
2012-10-10 06:13:05 +08:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
var (
|
|
|
|
ErrStop = fmt.Errorf("tail should now stop")
|
|
|
|
)
|
|
|
|
|
2012-10-10 06:13:05 +08:00
|
|
|
type Line struct {
|
2013-05-29 04:53:19 +08:00
|
|
|
Text string
|
|
|
|
Time time.Time
|
2013-09-18 07:54:55 +08:00
|
|
|
Err error // Error from tail
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
2013-11-15 01:44:28 +08:00
|
|
|
// NewLine returns a Line with present time.
|
|
|
|
func NewLine(text string) *Line {
|
|
|
|
return &Line{text, time.Now(), nil}
|
|
|
|
}
|
|
|
|
|
2013-08-10 06:15:40 +08:00
|
|
|
// SeekInfo represents arguments to `os.Seek`
|
2013-08-10 05:53:37 +08:00
|
|
|
type SeekInfo struct {
|
|
|
|
Offset int64
|
|
|
|
Whence int // os.SEEK_*
|
|
|
|
}
|
|
|
|
|
2013-05-30 07:32:01 +08:00
|
|
|
// Config is used to specify how a file must be tailed.
|
2012-10-13 05:32:04 +08:00
|
|
|
type Config struct {
|
2013-09-18 07:54:55 +08:00
|
|
|
// File-specifc
|
2014-04-30 07:39:57 +08:00
|
|
|
Location *SeekInfo // Seek to this location before tailing
|
|
|
|
ReOpen bool // Reopen recreated files (tail -F)
|
|
|
|
MustExist bool // Fail early if the file does not exist
|
|
|
|
Poll bool // Poll for file changes instead of using inotify
|
|
|
|
RateLimiter *ratelimiter.LeakyBucket
|
2013-09-18 07:54:55 +08:00
|
|
|
|
|
|
|
// Generic IO
|
|
|
|
Follow bool // Continue looking for new lines (tail -f)
|
|
|
|
MaxLineSize int // If non-zero, split longer lines into multiple lines
|
2014-01-30 20:47:28 +08:00
|
|
|
|
2014-02-22 22:48:23 +08:00
|
|
|
// Logger, when nil, is set to tail.DefaultLogger
|
|
|
|
// To disable logging: set field to tail.DiscardingLogger
|
|
|
|
Logger *log.Logger
|
2012-10-13 05:32:04 +08:00
|
|
|
}
|
|
|
|
|
2012-10-10 06:13:05 +08:00
|
|
|
type Tail struct {
|
|
|
|
Filename string
|
|
|
|
Lines chan *Line
|
2012-10-13 05:32:04 +08:00
|
|
|
Config
|
2012-10-10 06:13:05 +08:00
|
|
|
|
2012-10-13 05:32:04 +08:00
|
|
|
file *os.File
|
|
|
|
reader *bufio.Reader
|
2013-05-29 07:34:36 +08:00
|
|
|
watcher watch.FileWatcher
|
2013-05-30 05:32:59 +08:00
|
|
|
changes *watch.FileChanges
|
2012-10-10 06:13:05 +08:00
|
|
|
|
2012-10-13 07:28:22 +08:00
|
|
|
tomb.Tomb // provides: Done, Kill, Dying
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
2014-02-22 22:48:23 +08:00
|
|
|
var (
|
|
|
|
// DefaultLogger is used when Config.Logger == nil
|
|
|
|
DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
|
|
|
|
// DiscardingLogger can be used to disable logging output
|
|
|
|
DiscardingLogger = log.New(ioutil.Discard, "", 0)
|
|
|
|
)
|
|
|
|
|
2013-05-30 07:32:01 +08:00
|
|
|
// TailFile begins tailing the file. Output stream is made available
|
|
|
|
// via the `Tail.Lines` channel. To handle errors during tailing,
|
|
|
|
// invoke the `Wait` or `Err` method after finishing reading from the
|
|
|
|
// `Lines` channel.
|
2012-10-13 05:32:04 +08:00
|
|
|
func TailFile(filename string, config Config) (*Tail, error) {
|
2012-10-13 07:28:22 +08:00
|
|
|
if config.ReOpen && !config.Follow {
|
2013-10-12 09:19:43 +08:00
|
|
|
util.Fatal("cannot set ReOpen without Follow.")
|
2012-10-13 08:14:35 +08:00
|
|
|
}
|
|
|
|
|
2012-10-10 06:13:05 +08:00
|
|
|
t := &Tail{
|
2012-10-13 07:28:22 +08:00
|
|
|
Filename: filename,
|
|
|
|
Lines: make(chan *Line),
|
2014-01-30 20:47:28 +08:00
|
|
|
Config: config,
|
|
|
|
}
|
|
|
|
|
|
|
|
// when Logger was not specified in config, use default logger
|
|
|
|
if t.Logger == nil {
|
|
|
|
t.Logger = log.New(os.Stderr, "", log.LstdFlags)
|
|
|
|
}
|
2012-10-10 06:13:05 +08:00
|
|
|
|
2012-10-13 05:32:04 +08:00
|
|
|
if t.Poll {
|
2013-05-29 07:34:36 +08:00
|
|
|
t.watcher = watch.NewPollingFileWatcher(filename)
|
2012-10-12 12:29:06 +08:00
|
|
|
} else {
|
2013-05-29 07:34:36 +08:00
|
|
|
t.watcher = watch.NewInotifyFileWatcher(filename)
|
2012-10-12 12:29:06 +08:00
|
|
|
}
|
|
|
|
|
2012-10-13 05:32:04 +08:00
|
|
|
if t.MustExist {
|
|
|
|
var err error
|
|
|
|
t.file, err = os.Open(t.Filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go t.tailFileSync()
|
2012-10-10 06:13:05 +08:00
|
|
|
|
2012-10-11 06:13:13 +08:00
|
|
|
return t, nil
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
2013-08-23 18:04:21 +08:00
|
|
|
// Return the file's current position, like stdio's ftell().
|
|
|
|
// But this value is not very accurate.
|
2013-08-24 15:43:25 +08:00
|
|
|
// it may readed one line in the chan(tail.Lines),
|
|
|
|
// so it may lost one line.
|
|
|
|
func (tail *Tail) Tell() (offset int64, err error) {
|
|
|
|
if tail.file == nil {
|
|
|
|
return
|
|
|
|
}
|
2013-08-23 18:04:21 +08:00
|
|
|
offset, err = tail.file.Seek(0, os.SEEK_CUR)
|
|
|
|
if err == nil {
|
|
|
|
offset -= int64(tail.reader.Buffered())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
// Stop stops the tailing activity.
|
2012-10-13 07:28:22 +08:00
|
|
|
func (tail *Tail) Stop() error {
|
|
|
|
tail.Kill(nil)
|
|
|
|
return tail.Wait()
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (tail *Tail) close() {
|
|
|
|
close(tail.Lines)
|
|
|
|
if tail.file != nil {
|
|
|
|
tail.file.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-13 08:14:35 +08:00
|
|
|
func (tail *Tail) reopen() error {
|
2012-10-10 06:13:05 +08:00
|
|
|
if tail.file != nil {
|
|
|
|
tail.file.Close()
|
|
|
|
}
|
|
|
|
for {
|
|
|
|
var err error
|
|
|
|
tail.file, err = os.Open(tail.Filename)
|
|
|
|
if err != nil {
|
2012-10-13 05:32:04 +08:00
|
|
|
if os.IsNotExist(err) {
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
|
2013-09-23 15:03:51 +08:00
|
|
|
if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
|
|
|
|
if err == tomb.ErrDying {
|
|
|
|
return err
|
|
|
|
}
|
2012-10-13 08:14:35 +08:00
|
|
|
return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2012-10-13 08:14:35 +08:00
|
|
|
return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
2012-10-13 05:32:04 +08:00
|
|
|
break
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
2012-10-13 08:14:35 +08:00
|
|
|
return nil
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
2014-05-17 09:01:10 +08:00
|
|
|
func (tail *Tail) readLine() (string, error) {
|
|
|
|
line, err := tail.reader.ReadString('\n')
|
2014-05-17 07:46:31 +08:00
|
|
|
if err != nil {
|
2014-05-17 09:01:10 +08:00
|
|
|
// Note ReadString "returns the data read before the error" in
|
|
|
|
// case of an error, including EOF, so we return it as is. The
|
|
|
|
// caller is expected to process it if err is EOF.
|
2014-05-17 07:59:29 +08:00
|
|
|
return line, err
|
2014-05-17 07:46:31 +08:00
|
|
|
}
|
|
|
|
|
2014-05-17 09:01:10 +08:00
|
|
|
line = strings.TrimRight(line, "\n")
|
2013-10-11 17:00:29 +08:00
|
|
|
|
2014-05-17 09:01:10 +08:00
|
|
|
return line, err
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
2012-10-13 05:32:04 +08:00
|
|
|
func (tail *Tail) tailFileSync() {
|
2012-10-13 07:28:22 +08:00
|
|
|
defer tail.Done()
|
2013-05-30 04:57:02 +08:00
|
|
|
defer tail.close()
|
2012-10-13 07:28:22 +08:00
|
|
|
|
2012-10-13 05:32:04 +08:00
|
|
|
if !tail.MustExist {
|
2013-05-30 04:57:02 +08:00
|
|
|
// deferred first open.
|
2012-10-13 08:14:35 +08:00
|
|
|
err := tail.reopen()
|
|
|
|
if err != nil {
|
2013-09-24 09:13:19 +08:00
|
|
|
if err != tomb.ErrDying {
|
|
|
|
tail.Kill(err)
|
|
|
|
}
|
2012-10-13 08:14:35 +08:00
|
|
|
return
|
|
|
|
}
|
2012-10-13 05:32:04 +08:00
|
|
|
}
|
2012-10-10 06:13:05 +08:00
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
// Seek to requested location on first open of the file.
|
2013-08-10 05:53:37 +08:00
|
|
|
if tail.Location != nil {
|
|
|
|
_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
|
2012-10-10 06:13:05 +08:00
|
|
|
if err != nil {
|
2012-10-13 07:28:22 +08:00
|
|
|
tail.Killf("Seek error on %s: %s", tail.Filename, err)
|
2012-10-13 08:14:35 +08:00
|
|
|
return
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-17 08:03:16 +08:00
|
|
|
tail.openReader()
|
2012-10-10 06:13:05 +08:00
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
// Read line by line.
|
2012-10-10 06:13:05 +08:00
|
|
|
for {
|
|
|
|
line, err := tail.readLine()
|
|
|
|
|
2014-05-17 09:05:11 +08:00
|
|
|
if err == nil {
|
|
|
|
cooloff := !tail.sendLine(line)
|
|
|
|
if cooloff {
|
|
|
|
// Wait a second before seeking till the end of
|
|
|
|
// file when rate limit is reached.
|
|
|
|
msg := fmt.Sprintf(
|
|
|
|
"Too much log activity; waiting a second " +
|
|
|
|
"before resuming tailing")
|
|
|
|
tail.Lines <- &Line{msg, time.Now(), fmt.Errorf(msg)}
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Second):
|
|
|
|
case <-tail.Dying():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = tail.seekEnd()
|
|
|
|
if err != nil {
|
|
|
|
tail.Kill(err)
|
|
|
|
return
|
2013-07-12 08:28:18 +08:00
|
|
|
}
|
2012-10-12 12:29:06 +08:00
|
|
|
}
|
2014-05-17 09:05:11 +08:00
|
|
|
}else if err == io.EOF {
|
2013-05-31 04:18:46 +08:00
|
|
|
if !tail.Follow {
|
|
|
|
return
|
|
|
|
}
|
2013-05-30 04:57:02 +08:00
|
|
|
// When EOF is reached, wait for more data to become
|
|
|
|
// available. Wait strategy is based on the `tail.watcher`
|
|
|
|
// implementation (inotify or polling).
|
2013-05-30 05:32:59 +08:00
|
|
|
err := tail.waitForChanges()
|
2013-05-30 07:32:01 +08:00
|
|
|
if err != nil {
|
2013-05-30 04:57:02 +08:00
|
|
|
if err != ErrStop {
|
|
|
|
tail.Kill(err)
|
2012-10-12 12:29:06 +08:00
|
|
|
}
|
2013-05-30 04:57:02 +08:00
|
|
|
return
|
2012-10-12 12:29:06 +08:00
|
|
|
}
|
2014-05-17 09:05:11 +08:00
|
|
|
}else {
|
|
|
|
// non-EOF error
|
2013-05-30 04:57:02 +08:00
|
|
|
tail.Killf("Error reading %s: %s", tail.Filename, err)
|
|
|
|
return
|
2012-10-10 06:13:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
2012-10-13 07:28:22 +08:00
|
|
|
case <-tail.Dying():
|
2012-10-10 06:13:05 +08:00
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
// waitForChanges waits until the file has been appended, deleted,
|
2013-05-30 07:32:01 +08:00
|
|
|
// moved or truncated. When moved or deleted - the file will be
|
|
|
|
// reopened if ReOpen is true. Truncated files are always reopened.
|
2013-05-30 04:57:02 +08:00
|
|
|
func (tail *Tail) waitForChanges() error {
|
|
|
|
if tail.changes == nil {
|
|
|
|
st, err := tail.file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-09-23 15:03:51 +08:00
|
|
|
tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
2013-05-30 05:32:59 +08:00
|
|
|
case <-tail.changes.Modified:
|
2013-05-30 07:32:01 +08:00
|
|
|
return nil
|
2013-05-30 05:32:59 +08:00
|
|
|
case <-tail.changes.Deleted:
|
|
|
|
tail.changes = nil
|
|
|
|
if tail.ReOpen {
|
|
|
|
// XXX: we must not log from a library.
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
|
2013-05-30 05:32:59 +08:00
|
|
|
if err := tail.reopen(); err != nil {
|
|
|
|
return err
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Successfully reopened %s", tail.Filename)
|
2014-05-17 08:03:16 +08:00
|
|
|
tail.openReader()
|
2013-05-30 05:32:59 +08:00
|
|
|
return nil
|
|
|
|
} else {
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
|
2013-05-30 05:32:59 +08:00
|
|
|
return ErrStop
|
|
|
|
}
|
|
|
|
case <-tail.changes.Truncated:
|
|
|
|
// Always reopen truncated files (Follow is true)
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
|
2013-05-30 05:32:59 +08:00
|
|
|
if err := tail.reopen(); err != nil {
|
2013-05-30 07:32:01 +08:00
|
|
|
return err
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
|
2014-05-17 08:03:16 +08:00
|
|
|
tail.openReader()
|
2013-05-30 05:32:59 +08:00
|
|
|
return nil
|
2013-05-30 04:57:02 +08:00
|
|
|
case <-tail.Dying():
|
|
|
|
return ErrStop
|
|
|
|
}
|
2013-05-30 07:32:01 +08:00
|
|
|
panic("unreachable")
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
|
|
|
|
2014-05-17 08:03:16 +08:00
|
|
|
func (tail *Tail) openReader() {
|
2014-02-19 07:46:36 +08:00
|
|
|
if tail.MaxLineSize > 0 {
|
2014-04-29 05:14:21 +08:00
|
|
|
// add 2 to account for newline characters
|
2014-05-17 08:03:16 +08:00
|
|
|
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize+2)
|
2014-02-19 07:46:36 +08:00
|
|
|
} else {
|
2014-05-17 08:03:16 +08:00
|
|
|
tail.reader = bufio.NewReader(tail.file)
|
2014-02-19 07:46:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-29 05:55:51 +08:00
|
|
|
func (tail *Tail) seekEnd() error {
|
|
|
|
_, err := tail.file.Seek(0, 2)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Seek error on %s: %s", tail.Filename, err)
|
|
|
|
}
|
|
|
|
// Reset the read buffer whenever the file is re-seek'ed
|
|
|
|
tail.reader.Reset(tail.file)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
// sendLine sends the line(s) to Lines channel, splitting longer lines
|
2013-07-12 08:28:18 +08:00
|
|
|
// if necessary. Return false if rate limit is reached.
|
2014-05-17 09:01:10 +08:00
|
|
|
func (tail *Tail) sendLine(line string) bool {
|
2013-05-30 04:57:02 +08:00
|
|
|
now := time.Now()
|
2014-05-17 09:01:10 +08:00
|
|
|
lines := []string{line}
|
2013-05-30 04:57:02 +08:00
|
|
|
|
2013-07-12 08:28:18 +08:00
|
|
|
// Split longer lines
|
2013-05-30 04:57:02 +08:00
|
|
|
if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
|
2014-05-17 09:01:10 +08:00
|
|
|
lines = util.PartitionString(line, tail.MaxLineSize)
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
2013-05-30 07:32:01 +08:00
|
|
|
|
2013-05-30 04:57:02 +08:00
|
|
|
for _, line := range lines {
|
2013-07-12 08:28:18 +08:00
|
|
|
tail.Lines <- &Line{line, now, nil}
|
2014-04-30 07:39:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if tail.Config.RateLimiter != nil {
|
|
|
|
ok := tail.Config.RateLimiter.Pour(uint16(len(lines)))
|
|
|
|
if !ok {
|
|
|
|
tail.Logger.Printf("Leaky bucket full (%v); entering 1s cooloff period.\n",
|
2014-02-22 22:48:23 +08:00
|
|
|
tail.Filename)
|
2013-07-12 08:28:18 +08:00
|
|
|
return false
|
|
|
|
}
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
|
|
|
|
2013-07-12 08:28:18 +08:00
|
|
|
return true
|
2013-05-30 04:57:02 +08:00
|
|
|
}
|
2013-11-13 12:15:27 +08:00
|
|
|
|
2013-11-14 09:41:13 +08:00
|
|
|
// Cleanup removes inotify watches added by the tail package. This function is
|
2014-01-05 01:49:15 +08:00
|
|
|
// meant to be invoked from a process's exit handler. Linux kernel may not
|
2013-11-14 09:41:13 +08:00
|
|
|
// automatically remove inotify watches after the process exits.
|
2013-11-13 12:15:27 +08:00
|
|
|
func Cleanup() {
|
|
|
|
watch.Cleanup()
|
|
|
|
}
|