logmower-shipper/vendor/github.com/jtagcat/util/tail/shared.go

144 lines
2.4 KiB
Go

package tail
import (
"bufio"
"context"
"errors"
"io"
"os"
"sync"
)
type Line struct {
Filename *string
Bytes []byte
EndOffset int64 // io.SeekStart
ReachedEOF bool
}
type Tailable struct {
Name string
// os.Seek() on first open:
Offset int64
Whence int
// loop-persisting
existed bool
// copied use
wakeup chan struct{}
}
type orderedLines struct {
c chan<- *Line
sync.Mutex // guarantee ordered lines across multiple files of same name
}
// Handles tailing a file within its lifespan
func fileHandle(ctx context.Context, file Tailable, useOffset bool, lineChan *orderedLines, errChan chan<- error) {
f, err := os.Open(file.Name)
if err != nil && !errors.Is(err, os.ErrNotExist) { // ignore ErrNotExist, as it may have been race deleted
errChan <- err
return
}
defer f.Close()
var offset int64
if useOffset {
offset, err = f.Seek(file.Offset, file.Whence)
if err != nil {
errChan <- err
return
}
}
first, breakNext, b := true, false, bufio.NewReader(f)
for {
select {
case <-ctx.Done():
errChan <- ctx.Err()
return
default:
}
if first {
first = false
} else {
offset, err = detectTruncation(f, offset)
if err != nil {
errChan <- err
return
}
}
offset, err = readToEOF(b, &file.Name, offset, lineChan.c)
if err != nil {
errChan <- err
return
}
if breakNext {
return
}
select {
case <-ctx.Done():
case _, ok := <-file.wakeup:
if !ok {
fs, err := f.Stat()
if err != nil && !errors.Is(err, os.ErrNotExist) {
errChan <- ctx.Err()
return
}
if err != nil || fs.Size() == offset {
return
}
breakNext = true
}
}
}
}
// offset is io.SeekStart
func detectTruncation(f *os.File, offset int64) (int64, error) {
fs, err := f.Stat()
if err != nil && !errors.Is(err, os.ErrNotExist) { // ignore ErrNotExist, as it may have been race deleted
return 0, err
}
if fs.Size() < offset {
// file has been truncated
return f.Seek(0, io.SeekStart)
}
return offset, nil
}
func readToEOF(buf *bufio.Reader, name *string, offset int64, c chan<- *Line) (int64, error) {
for {
b, err := buf.ReadBytes('\n')
offset += int64(len(b))
if err != nil && !errors.Is(err, io.EOF) {
return offset, err
}
if err == nil {
b = b[:len(b)-1] // remove \n
}
c <- &Line{
Filename: name,
Bytes: b,
EndOffset: offset,
ReachedEOF: err != nil,
}
if err != nil { // EOF
return offset, nil
}
}
}