use struct to configure the growing tail args; start working on unit tests
This commit is contained in:
parent
f7193d4658
commit
187dea7196
|
@ -0,0 +1,4 @@
|
||||||
|
default: test
|
||||||
|
|
||||||
|
test: *.go
|
||||||
|
GOPATH=~/as/logyard go test
|
67
tail.go
67
tail.go
|
@ -14,15 +14,23 @@ type Line struct {
|
||||||
UnixTime int64
|
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 {
|
type Tail struct {
|
||||||
Filename string
|
Filename string
|
||||||
Lines chan *Line
|
Lines chan *Line
|
||||||
|
Config
|
||||||
|
|
||||||
useinotify bool
|
file *os.File
|
||||||
maxlinesize int
|
reader *bufio.Reader
|
||||||
file *os.File
|
watcher FileWatcher
|
||||||
reader *bufio.Reader
|
|
||||||
watcher FileWatcher
|
|
||||||
|
|
||||||
stop chan bool
|
stop chan bool
|
||||||
created chan bool
|
created chan bool
|
||||||
|
@ -31,26 +39,38 @@ type Tail struct {
|
||||||
// TailFile channels the lines of a logfile along with timestamp. If
|
// TailFile channels the lines of a logfile along with timestamp. If
|
||||||
// end is true, channel only newly added lines. If retry is true, tail
|
// 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.
|
// 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{
|
t := &Tail{
|
||||||
filename,
|
filename,
|
||||||
make(chan *Line),
|
make(chan *Line),
|
||||||
useinotify,
|
config,
|
||||||
maxlinesize,
|
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
make(chan bool),
|
make(chan bool),
|
||||||
make(chan bool)}
|
make(chan bool)}
|
||||||
|
|
||||||
if !useinotify {
|
if t.Poll {
|
||||||
log.Println("Warning: not using inotify; will poll ", filename)
|
log.Println("Warning: not using inotify; will poll ", filename)
|
||||||
t.watcher = NewPollingFileWatcher(filename)
|
t.watcher = NewPollingFileWatcher(filename)
|
||||||
} else {
|
} else {
|
||||||
t.watcher = NewInotifyFileWatcher(filename)
|
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
|
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 {
|
if tail.file != nil {
|
||||||
tail.file.Close()
|
tail.file.Close()
|
||||||
}
|
}
|
||||||
|
@ -75,18 +95,17 @@ func (tail *Tail) reopen(wait bool) {
|
||||||
var err error
|
var err error
|
||||||
tail.file, err = os.Open(tail.Filename)
|
tail.file, err = os.Open(tail.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) && wait {
|
if os.IsNotExist(err) {
|
||||||
err := tail.watcher.BlockUntilExists()
|
err := tail.watcher.BlockUntilExists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
// TODO: use error channels
|
||||||
|
log.Fatalf("cannot watch for file creation -- %s", tail.Filename, err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Println(fmt.Sprintf("Unable to reopen file (%s): %s", tail.Filename, err))
|
|
||||||
}
|
}
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
return // unreachable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tail *Tail) readLine() ([]byte, error) {
|
func (tail *Tail) readLine() ([]byte, error) {
|
||||||
|
@ -105,14 +124,16 @@ func (tail *Tail) readLine() ([]byte, error) {
|
||||||
return line, err
|
return line, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tail *Tail) tailFileSync(end bool, retry bool) {
|
func (tail *Tail) tailFileSync() {
|
||||||
tail.reopen(retry)
|
if !tail.MustExist {
|
||||||
|
tail.reopen()
|
||||||
|
}
|
||||||
|
|
||||||
var changes chan bool
|
var changes chan bool
|
||||||
|
|
||||||
// Note: seeking to end happens only at the beginning; never
|
// Note: seeking to end happens only at the beginning; never
|
||||||
// during subsequent re-opens.
|
// during subsequent re-opens.
|
||||||
if end {
|
if tail.Location == 0 {
|
||||||
_, err := tail.file.Seek(0, 2) // seek to end of the file
|
_, err := tail.file.Seek(0, 2) // seek to end of the file
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: don't panic here
|
// 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 {
|
for {
|
||||||
line, err := tail.readLine()
|
line, err := tail.readLine()
|
||||||
|
@ -148,11 +169,11 @@ func (tail *Tail) tailFileSync(end bool, retry bool) {
|
||||||
case _, ok := <-changes:
|
case _, ok := <-changes:
|
||||||
if !ok {
|
if !ok {
|
||||||
// file got deleted/renamed
|
// file got deleted/renamed
|
||||||
if retry {
|
if tail.ReOpen {
|
||||||
log.Printf("File %s has been moved (logrotation?); reopening..", tail.Filename)
|
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)
|
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
|
changes = nil
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue