diff --git a/tail.go b/tail.go index cedbb23..14f33a3 100644 --- a/tail.go +++ b/tail.go @@ -211,10 +211,17 @@ func (tail *Tail) tailFileSync() { // Read line by line. for { + // grab the position in case we need to back up in the event of a half-line + offset, err := tail.Tell() + if err != nil { + tail.Kill(err) + return + } + line, err := tail.readLine() // Process `line` even if err is EOF. - if err == nil || (err == io.EOF && line != "") { + if err == nil { cooloff := !tail.sendLine(line) if cooloff { // Wait a second before seeking till the end of @@ -236,8 +243,22 @@ func (tail *Tail) tailFileSync() { } } else if err == io.EOF { if !tail.Follow { + if line != "" { + tail.sendLine(line) + } return } + + if tail.Follow && line != "" { + // this has the potential to never return the last line if + // it's not followed by a newline; seems a fair trade here + err := tail.seekTo(SeekInfo{Offset: offset, Whence: 0}) + if err != nil { + tail.Kill(err) + return + } + } + // When EOF is reached, wait for more data to become // available. Wait strategy is based on the `tail.watcher` // implementation (inotify or polling). @@ -317,7 +338,11 @@ func (tail *Tail) openReader() { } func (tail *Tail) seekEnd() error { - _, err := tail.file.Seek(0, 2) + return tail.seekTo(SeekInfo{Offset: 0, Whence: 2}) +} + +func (tail *Tail) seekTo(pos SeekInfo) error { + _, err := tail.file.Seek(pos.Offset, pos.Whence) if err != nil { return fmt.Errorf("Seek error on %s: %s", tail.Filename, err) } diff --git a/tail_test.go b/tail_test.go index 0b9c5f9..c20e2b6 100644 --- a/tail_test.go +++ b/tail_test.go @@ -54,11 +54,11 @@ func TestStop(t *testing.T) { Cleanup() } -func TestMaxLineSize(_t *testing.T) { +func MaxLineSizeT(_t *testing.T, follow bool, fileContent string, expected []string) { t := NewTailTest("maxlinesize", _t) - t.CreateFile("test.txt", "hello\nworld\nfin\nhe") - tail := t.StartTail("test.txt", Config{Follow: true, Location: nil, MaxLineSize: 3}) - go t.VerifyTailOutput(tail, []string{"hel", "lo", "wor", "ld", "fin", "he"}) + t.CreateFile("test.txt", fileContent) + tail := t.StartTail("test.txt", Config{Follow: follow, Location: nil, MaxLineSize: 3}) + go t.VerifyTailOutput(tail, expected) // Delete after a reasonable delay, to give tail sufficient time // to read all lines. @@ -68,6 +68,15 @@ func TestMaxLineSize(_t *testing.T) { Cleanup() } +func TestMaxLineSizeFollow(_t *testing.T) { + // As last file line does not end with newline, it will not be present in tail's output + MaxLineSizeT(_t, true, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin"}) +} + +func TestMaxLineSizeNoFollow(_t *testing.T) { + MaxLineSizeT(_t, false, "hello\nworld\nfin\nhe", []string{"hel", "lo", "wor", "ld", "fin", "he"}) +} + func TestOver4096ByteLine(_t *testing.T) { t := NewTailTest("Over4096ByteLine", _t) testString := strings.Repeat("a", 4097)