use struct to configure the growing tail args; start working on unit tests

This commit is contained in:
Sridhar Ratnakumar 2012-10-12 14:32:04 -07:00
parent f7193d4658
commit 187dea7196
3 changed files with 65 additions and 23 deletions

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
default: test
test: *.go
GOPATH=~/as/logyard go test

61
tail.go
View File

@ -14,12 +14,20 @@ type Line struct {
UnixTime int64
}
type Config struct {
Location int // -n
Follow bool // -f
ReOpen bool // -F
MustExist bool // if false, wait for the file to exist before beginning to tail.
Poll bool // if true, do not use inotify but use polling
MaxLineSize int // if > 0, limit the line size (discarding the rest)
}
type Tail struct {
Filename string
Lines chan *Line
Config
useinotify bool
maxlinesize int
file *os.File
reader *bufio.Reader
watcher FileWatcher
@ -31,26 +39,38 @@ type Tail struct {
// TailFile channels the lines of a logfile along with timestamp. If
// end is true, channel only newly added lines. If retry is true, tail
// the file name (not descriptor) and retry on file open/read errors.
func TailFile(filename string, maxlinesize int, end bool, retry bool, useinotify bool) (*Tail, error) {
// func TailFile(filename string, maxlinesize int, end bool, retry bool, useinotify bool) (*Tail, error) {
func TailFile(filename string, config Config) (*Tail, error) {
if !(config.Location == 0 || config.Location == -1) {
panic("only 0/-1 values are supported for Location")
}
t := &Tail{
filename,
make(chan *Line),
useinotify,
maxlinesize,
config,
nil,
nil,
nil,
make(chan bool),
make(chan bool)}
if !useinotify {
if t.Poll {
log.Println("Warning: not using inotify; will poll ", filename)
t.watcher = NewPollingFileWatcher(filename)
} else {
t.watcher = NewInotifyFileWatcher(filename)
}
go t.tailFileSync(end, retry)
if t.MustExist {
var err error
t.file, err = os.Open(t.Filename)
if err != nil {
return nil, err
}
}
go t.tailFileSync()
return t, nil
}
@ -67,7 +87,7 @@ func (tail *Tail) close() {
}
}
func (tail *Tail) reopen(wait bool) {
func (tail *Tail) reopen() {
if tail.file != nil {
tail.file.Close()
}
@ -75,18 +95,17 @@ func (tail *Tail) reopen(wait bool) {
var err error
tail.file, err = os.Open(tail.Filename)
if err != nil {
if os.IsNotExist(err) && wait {
if os.IsNotExist(err) {
err := tail.watcher.BlockUntilExists()
if err != nil {
panic(err)
// TODO: use error channels
log.Fatalf("cannot watch for file creation -- %s", tail.Filename, err)
}
continue
}
log.Println(fmt.Sprintf("Unable to reopen file (%s): %s", tail.Filename, err))
}
return
break
}
return // unreachable
}
func (tail *Tail) readLine() ([]byte, error) {
@ -105,14 +124,16 @@ func (tail *Tail) readLine() ([]byte, error) {
return line, err
}
func (tail *Tail) tailFileSync(end bool, retry bool) {
tail.reopen(retry)
func (tail *Tail) tailFileSync() {
if !tail.MustExist {
tail.reopen()
}
var changes chan bool
// Note: seeking to end happens only at the beginning; never
// during subsequent re-opens.
if end {
if tail.Location == 0 {
_, err := tail.file.Seek(0, 2) // seek to end of the file
if err != nil {
// TODO: don't panic here
@ -120,7 +141,7 @@ func (tail *Tail) tailFileSync(end bool, retry bool) {
}
}
tail.reader = bufio.NewReaderSize(tail.file, tail.maxlinesize)
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize)
for {
line, err := tail.readLine()
@ -148,11 +169,11 @@ func (tail *Tail) tailFileSync(end bool, retry bool) {
case _, ok := <-changes:
if !ok {
// file got deleted/renamed
if retry {
if tail.ReOpen {
log.Printf("File %s has been moved (logrotation?); reopening..", tail.Filename)
tail.reopen(retry)
tail.reopen()
log.Printf("File %s has been reopened.", tail.Filename)
tail.reader = bufio.NewReaderSize(tail.file, tail.maxlinesize)
tail.reader = bufio.NewReaderSize(tail.file, tail.MaxLineSize)
changes = nil
continue
} else {

17
tail_test.go Normal file
View File

@ -0,0 +1,17 @@
package tail
import (
"testing"
)
// Test Config.MustExist
func TestMissingFile(t *testing.T) {
_, err := TailFile("/no/such/file", Config{Follow: true, MustExist: true})
if err == nil {
t.Error("MustExist:true is violated")
}
_, err = TailFile("README.md", Config{Follow: true, MustExist: false})
if err != nil {
t.Error("MustExist:false is violated")
}
}