diff --git a/README.md b/README.md index 3f8c1c4..6cb80d3 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,17 @@ for line := range t.Lines { See [API documentation](http://godoc.org/github.com/ActiveState/tail). +## Log rotation + +Tail comes with full support for truncation/move detection as it is +designed to work with log rotation tools. + ## Installing - go get github.com/ActiveState/tail - -## Building - -To build and test the package, - - make test - -To build the toy command-line program `gotail`, - - cd cmd/gotail - make - ./gotail -h + go get github.com/ActiveState/tail/... ## TODO -* Support arbitrary values for `Location` +* Arbitrary values for `Location` +* `Location` could be specified in both lines and bytes metrics. diff --git a/tail.go b/tail.go index 68df120..e6d611c 100644 --- a/tail.go +++ b/tail.go @@ -22,7 +22,7 @@ type Line struct { Time time.Time } -// Tail configuration +// Config is used to specify how a file must be tailed. type Config struct { Location int // Tail from last N lines (tail -n) Follow bool // Continue looking for new lines (tail -f) @@ -45,10 +45,10 @@ type Tail struct { tomb.Tomb // provides: Done, Kill, Dying } -// TailFile begins tailing the file with the specified -// configuration. Output stream is made available via the `Tail.Lines` -// channel. To handle errors during tailing, invoke the `Wait` method -// after finishing reading from the `Lines` channel. +// 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. func TailFile(filename string, config Config) (*Tail, error) { if !(config.Location == 0 || config.Location == -1) { panic("only 0/-1 values are supported for Location.") @@ -154,7 +154,7 @@ func (tail *Tail) tailFileSync() { for { line, err := tail.readLine() - switch(err) { + switch err { case nil: if line != nil { tail.sendLine(line) @@ -164,7 +164,7 @@ func (tail *Tail) tailFileSync() { // available. Wait strategy is based on the `tail.watcher` // implementation (inotify or polling). err := tail.waitForChanges() - if err != nil { + if err != nil { if err != ErrStop { tail.Kill(err) } @@ -184,8 +184,8 @@ func (tail *Tail) tailFileSync() { } // waitForChanges waits until the file has been appended, deleted, -// moved or truncated. When moved, deleted or truncated - the file -// will be re-opened if ReOpen is true. +// moved or truncated. When moved or deleted - the file will be +// reopened if ReOpen is true. Truncated files are always reopened. func (tail *Tail) waitForChanges() error { if tail.changes == nil { st, err := tail.file.Stat() @@ -197,6 +197,7 @@ func (tail *Tail) waitForChanges() error { select { case <-tail.changes.Modified: + return nil case <-tail.changes.Deleted: tail.changes = nil if tail.ReOpen { @@ -216,7 +217,7 @@ func (tail *Tail) waitForChanges() error { // Always reopen truncated files (Follow is true) log.Printf("Re-opening truncated file %s ...", tail.Filename) if err := tail.reopen(); err != nil { - return err + return err } log.Printf("Successfully reopened truncated %s", tail.Filename) tail.reader = bufio.NewReader(tail.file) @@ -224,7 +225,7 @@ func (tail *Tail) waitForChanges() error { case <-tail.Dying(): return ErrStop } - return nil + panic("unreachable") } // sendLine sends the line(s) to Lines channel, splitting longer lines @@ -238,7 +239,7 @@ func (tail *Tail) sendLine(line []byte) { lines = partitionString( string(line), tail.MaxLineSize) } - + for _, line := range lines { tail.Lines <- &Line{line, now} } diff --git a/watch/watch.go b/watch/watch.go index 7840b8c..afff103 100644 --- a/watch/watch.go +++ b/watch/watch.go @@ -9,13 +9,13 @@ import ( // FileWatcher monitors file-level events. type FileWatcher interface { - // BlockUntilExists blocks until the missing file comes into - // existence. If the file already exists, returns immediately. + // BlockUntilExists blocks until the file comes into existence. BlockUntilExists(tomb.Tomb) error - // ChangeEvents returns a channel of events corresponding to the - // times the file is ready to be read. The channel will be closed - // if the file gets deleted, renamed or truncated. + // ChangeEvents reports on changes to a file, be it modification, + // deletion, renames or truncations. Returned FileChanges group of + // channels will be closed, thus become unusable, after a deletion + // or truncation event. ChangeEvents(tomb.Tomb, os.FileInfo) *FileChanges }