Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added line num to Line struct #149

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
default: test

test: *.go
go test -v -race ./...
go test -v -race -timeout 30s ./...

fmt:
gofmt -w .
Expand Down
19 changes: 12 additions & 7 deletions tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/hpcloud/tail/ratelimiter"
"github.com/hpcloud/tail/util"
"github.com/hpcloud/tail/watch"
"gopkg.in/tomb.v1"
tomb "gopkg.in/tomb.v1"
)

var (
Expand All @@ -27,13 +27,14 @@ var (

type Line struct {
Text string
Num int
Time time.Time
Err error // Error from tail
}

// NewLine returns a Line with present time.
func NewLine(text string) *Line {
return &Line{text, time.Now(), nil}
func NewLine(text string, lineNum int) *Line {
return &Line{text, lineNum, time.Now(), nil}
}

// SeekInfo represents arguments to `os.Seek`
Expand Down Expand Up @@ -78,8 +79,9 @@ type Tail struct {
Lines chan *Line
Config

file *os.File
reader *bufio.Reader
file *os.File
reader *bufio.Reader
lineNum int

watcher watch.FileWatcher
changes *watch.FileChanges
Expand Down Expand Up @@ -186,6 +188,8 @@ func (tail *Tail) closeFile() {

func (tail *Tail) reopen() error {
tail.closeFile()
// reset line number
tail.lineNum = 0
for {
var err error
tail.file, err = OpenFile(tail.Filename)
Expand Down Expand Up @@ -275,7 +279,7 @@ func (tail *Tail) tailFileSync() {
// file when rate limit is reached.
msg := ("Too much log activity; waiting a second " +
"before resuming tailing")
tail.Lines <- &Line{msg, time.Now(), errors.New(msg)}
tail.Lines <- &Line{msg, tail.lineNum, time.Now(), errors.New(msg)}
select {
case <-time.After(time.Second):
case <-tail.Dying():
Expand Down Expand Up @@ -414,7 +418,8 @@ func (tail *Tail) sendLine(line string) bool {
}

for _, line := range lines {
tail.Lines <- &Line{line, now, nil}
tail.lineNum++
tail.Lines <- &Line{line, tail.lineNum, now, nil}
}

if tail.Config.RateLimiter != nil {
Expand Down
139 changes: 117 additions & 22 deletions tail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ func TestStopAtEOF(t *testing.T) {
if line.Text != "hello" {
t.Errorf("Expected to get 'hello', got '%s' instead", line.Text)
}
if line.Num != 1 {
t.Errorf("Expected to get 1, got %d instead", line.Num)
}

tailTest.VerifyTailOutput(tail, []string{"there", "world"}, false)
tail.StopAtEOF()
Expand Down Expand Up @@ -134,6 +137,7 @@ func TestOver4096ByteLine(t *testing.T) {
tailTest.RemoveFile("test.txt")
tailTest.Cleanup(tail, true)
}

func TestOver4096ByteLineWithSetMaxLineSize(t *testing.T) {
tailTest := NewTailTest("Over4096ByteLineMaxLineSize", t)
testString := strings.Repeat("a", 4097)
Expand Down Expand Up @@ -219,6 +223,40 @@ func TestReOpenPolling(t *testing.T) {
reOpen(t, true)
}

func TestReOpenWithCursor(t *testing.T) {
delay := 300 * time.Millisecond // account for POLL_DURATION
tailTest := NewTailTest("reopen-cursor", t)
tailTest.CreateFile("test.txt", "hello\nworld\n")
tail := tailTest.StartTail(
"test.txt",
Config{Follow: true, ReOpen: true, Poll: true})
content := []string{"hello", "world", "more", "data", "endofworld"}
go tailTest.VerifyTailOutputUsingCursor(tail, content, false)

// deletion must trigger reopen
<-time.After(delay)
tailTest.RemoveFile("test.txt")
<-time.After(delay)
tailTest.CreateFile("test.txt", "hello\nworld\nmore\ndata\n")

// rename must trigger reopen
<-time.After(delay)
tailTest.RenameFile("test.txt", "test.txt.rotated")
<-time.After(delay)
tailTest.CreateFile("test.txt", "hello\nworld\nmore\ndata\nendofworld\n")

// Delete after a reasonable delay, to give tail sufficient time
// to read all lines.
<-time.After(delay)
tailTest.RemoveFile("test.txt")
<-time.After(delay)

// Do not bother with stopping as it could kill the tomb during
// the reading of data written above. Timings can vary based on
// test environment.
tailTest.Cleanup(tail, false)
}

// The use of polling file watcher could affect file rotation
// (detected via renames), so test these explicitly.

Expand All @@ -230,6 +268,31 @@ func TestReSeekPolling(t *testing.T) {
reSeek(t, true)
}

func TestReSeekWithCursor(t *testing.T) {
tailTest := NewTailTest("reseek-cursor", t)
tailTest.CreateFile("test.txt", "a really long string goes here\nhello\nworld\n")
tail := tailTest.StartTail(
"test.txt",
Config{Follow: true, ReOpen: false, Poll: false})

go tailTest.VerifyTailOutputUsingCursor(tail, []string{
"a really long string goes here", "hello", "world", "but", "not", "me"}, false)

// truncate now
<-time.After(100 * time.Millisecond)
tailTest.TruncateFile("test.txt", "skip\nme\nplease\nbut\nnot\nme\n")

// Delete after a reasonable delay, to give tail sufficient time
// to read all lines.
<-time.After(100 * time.Millisecond)
tailTest.RemoveFile("test.txt")

// Do not bother with stopping as it could kill the tomb during
// the reading of data written above. Timings can vary based on
// test environment.
tailTest.Cleanup(tail, false)
}

func TestRateLimiting(t *testing.T) {
tailTest := NewTailTest("rate-limiting", t)
tailTest.CreateFile("test.txt", "hello\nworld\nagain\nextra\n")
Expand Down Expand Up @@ -266,7 +329,10 @@ func TestTell(t *testing.T) {
Location: &SeekInfo{0, os.SEEK_SET}}
tail := tailTest.StartTail("test.txt", config)
// read noe line
<-tail.Lines
line := <-tail.Lines
if line.Num != 1 {
tailTest.Errorf("expected line to have number 1 but got %d", line.Num)
}
offset, err := tail.Tell()
if err != nil {
tailTest.Errorf("Tell return error: %s", err.Error())
Expand All @@ -285,6 +351,9 @@ func TestTell(t *testing.T) {
tailTest.Fatalf("mismatch; expected world or again, but got %s",
l.Text)
}
if l.Num < 1 || l.Num > 2 {
tailTest.Errorf("expected line number to be between 1 and 2 but got %d", l.Num)
}
break
}
tailTest.RemoveFile("test.txt")
Expand Down Expand Up @@ -518,7 +587,7 @@ func (t TailTest) StartTail(name string, config Config) *Tail {

func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) {
defer close(t.done)
t.ReadLines(tail, lines)
t.ReadLines(tail, lines, false)
// It is important to do this if only EOF is expected
// otherwise we could block on <-tail.Lines
if expectEOF {
Expand All @@ -529,27 +598,53 @@ func (t TailTest) VerifyTailOutput(tail *Tail, lines []string, expectEOF bool) {
}
}

func (t TailTest) ReadLines(tail *Tail, lines []string) {
for idx, line := range lines {
tailedLine, ok := <-tail.Lines
if !ok {
// tail.Lines is closed and empty.
err := tail.Err()
if err != nil {
t.Fatalf("tail ended with error: %v", err)
}
t.Fatalf("tail ended early; expecting more: %v", lines[idx:])
}
if tailedLine == nil {
t.Fatalf("tail.Lines returned nil; not possible")
func (t TailTest) VerifyTailOutputUsingCursor(tail *Tail, lines []string, expectEOF bool) {
defer close(t.done)
t.ReadLines(tail, lines, true)
// It is important to do this if only EOF is expected
// otherwise we could block on <-tail.Lines
if expectEOF {
line, ok := <-tail.Lines
if ok {
t.Fatalf("more content from tail: %+v", line)
}
// Note: not checking .Err as the `lines` argument is designed
// to match error strings as well.
if tailedLine.Text != line {
t.Fatalf(
"unexpected line/err from tail: "+
"expecting <<%s>>>, but got <<<%s>>>",
line, tailedLine.Text)
}
}

func (t TailTest) ReadLines(tail *Tail, lines []string, useCursor bool) {
cursor := 1

for _, line := range lines {
for {
tailedLine, ok := <-tail.Lines
if !ok {
// tail.Lines is closed and empty.
err := tail.Err()
if err != nil {
t.Fatalf("tail ended with error: %v", err)
}
t.Fatalf("tail ended early; expecting more: %v", lines[cursor:])
}
if tailedLine == nil {
t.Fatalf("tail.Lines returned nil; not possible")
}

if useCursor && tailedLine.Num < cursor {
// skip lines up until cursor
continue
}

// Note: not checking .Err as the `lines` argument is designed
// to match error strings as well.
if tailedLine.Text != line {
t.Fatalf(
"unexpected line/err from tail: "+
"expecting <<%s>>>, but got <<<%s>>>",
line, tailedLine.Text)
}

cursor++
break
}
}
}
Expand Down