diff options
Diffstat (limited to 'internal/io/fs')
| -rw-r--r-- | internal/io/fs/catfile.go | 3 | ||||
| -rw-r--r-- | internal/io/fs/filereader.go | 6 | ||||
| -rw-r--r-- | internal/io/fs/filter.go | 167 | ||||
| -rw-r--r-- | internal/io/fs/permissions/permission.go | 4 | ||||
| -rw-r--r-- | internal/io/fs/permissions/permission_linuxacl.go | 2 | ||||
| -rw-r--r-- | internal/io/fs/readfile.go | 334 | ||||
| -rw-r--r-- | internal/io/fs/readfilelcontext.go | 214 | ||||
| -rw-r--r-- | internal/io/fs/tailfile.go | 3 | ||||
| -rw-r--r-- | internal/io/fs/truncate.go | 61 |
9 files changed, 449 insertions, 345 deletions
diff --git a/internal/io/fs/catfile.go b/internal/io/fs/catfile.go index 7f387bc..e4676f3 100644 --- a/internal/io/fs/catfile.go +++ b/internal/io/fs/catfile.go @@ -6,7 +6,7 @@ type CatFile struct { } // NewCatFile returns a new file catter. -func NewCatFile(filePath string, globID string, serverMessages chan<- string, limiter chan struct{}) CatFile { +func NewCatFile(filePath string, globID string, serverMessages chan<- string) CatFile { return CatFile{ readFile: readFile{ filePath: filePath, @@ -15,7 +15,6 @@ func NewCatFile(filePath string, globID string, serverMessages chan<- string, li retry: false, canSkipLines: false, seekEOF: false, - limiter: limiter, }, } } diff --git a/internal/io/fs/filereader.go b/internal/io/fs/filereader.go index efd410e..e27d2a7 100644 --- a/internal/io/fs/filereader.go +++ b/internal/io/fs/filereader.go @@ -8,9 +8,11 @@ import ( "github.com/mimecast/dtail/internal/regex" ) -// FileReader is the interface used on the dtail server to read/cat/grep/mapr... a file. +// FileReader is the interface used on the dtail server to read/cat/grep/mapr... +// a file. type FileReader interface { - Start(ctx context.Context, lContext lcontext.LContext, lines chan<- line.Line, re regex.Regex) error + Start(ctx context.Context, ltx lcontext.LContext, lines chan<- *line.Line, + re regex.Regex) error FilePath() string Retry() bool } diff --git a/internal/io/fs/filter.go b/internal/io/fs/filter.go deleted file mode 100644 index c4f605e..0000000 --- a/internal/io/fs/filter.go +++ /dev/null @@ -1,167 +0,0 @@ -package fs - -import ( - "context" - - "github.com/mimecast/dtail/internal/io/line" - "github.com/mimecast/dtail/internal/lcontext" - "github.com/mimecast/dtail/internal/regex" -) - -func (f readFile) filter(ctx context.Context, rawLines <-chan []byte, lines chan<- line.Line, re regex.Regex, lContext lcontext.LContext) { - // Do we have any kind of local context settings? If so then run the more complex - // filterWithLContext method. - if lContext.Has() { - // We can not skip transmitting any lines to the client with a local - // grep context specified. - f.canSkipLines = false - f.filterWithLContext(ctx, rawLines, lines, re, lContext) - return - } - - f.filterWithoutLContext(ctx, rawLines, lines, re) -} - -// Filter log lines matching a given regular expression, however with local grep context. -func (f readFile) filterWithLContext(ctx context.Context, rawLines <-chan []byte, lines chan<- line.Line, re regex.Regex, lContext lcontext.LContext) { - // Scenario 1: Finish once maxCount hits found - maxCount := lContext.MaxCount - processMaxCount := maxCount > 0 - maxReached := false - - // Scenario 2: Print prev. N lines when current line matches. - before := lContext.BeforeContext - processBefore := before > 0 - var beforeBuf chan []byte - if processBefore { - beforeBuf = make(chan []byte, before) - } - - // Screnario 3: Print next N lines when current line matches. - after := 0 - processAfter := lContext.AfterContext > 0 - - for rawLine := range rawLines { - // logger.Debug("rawLine", string(rawLine)) - f.updatePosition() - - if !re.Match(rawLine) { - f.updateLineNotMatched() - - if processAfter && after > 0 { - after-- - myLine := line.Line{Content: rawLine, SourceID: f.globID, Count: f.totalLineCount(), TransmittedPerc: 100} - select { - case lines <- myLine: - case <-ctx.Done(): - return - } - - } else if processBefore { - // Keep last num BeforeContext raw messages. - select { - case beforeBuf <- rawLine: - default: - <-beforeBuf - beforeBuf <- rawLine - } - } - continue - } - - f.updateLineMatched() - - if processAfter { - if maxReached { - return - } - after = lContext.AfterContext - } - - if processBefore { - i := uint64(len(beforeBuf)) - for { - select { - case myRawLine := <-beforeBuf: - myLine := line.Line{Content: myRawLine, SourceID: f.globID, Count: f.totalLineCount() - i, TransmittedPerc: 100} - i-- - select { - case lines <- myLine: - case <-ctx.Done(): - return - } - default: - // beforeBuf is now empty. - } - if len(beforeBuf) == 0 { - break - } - } - } - - line := line.Line{Content: rawLine, SourceID: f.globID, Count: f.totalLineCount(), TransmittedPerc: 100} - - select { - case lines <- line: - if processMaxCount { - maxCount-- - if maxCount == 0 { - if !processAfter || after == 0 { - return - } - // Unfortunatley we have to continue filter, as there might be more lines to print - maxReached = true - } - } - case <-ctx.Done(): - return - } - } -} - -// Filter log lines matching a given regular expression, there is no local grep context specified. -func (f readFile) filterWithoutLContext(ctx context.Context, rawLines <-chan []byte, lines chan<- line.Line, re regex.Regex) { - for { - select { - case rawLine, ok := <-rawLines: - f.updatePosition() - if !ok { - return - } - - if f.lineUntransmittable(rawLine, len(lines), cap(lines), re) { - continue - } - - line := line.Line{Content: rawLine, SourceID: f.globID, Count: f.totalLineCount(), TransmittedPerc: f.transmittedPerc()} - - select { - case lines <- line: - continue - case <-ctx.Done(): - return - } - } - } -} - -func (f readFile) lineUntransmittable(rawLine []byte, length, capacity int, re regex.Regex) bool { - if !re.Match(rawLine) { - f.updateLineNotMatched() - f.updateLineNotTransmitted() - // Regex dosn't match, so not interested in it. - return true - } - f.updateLineMatched() - - // Can we actually send more messages, channel capacity reached? - if f.canSkipLines && length >= capacity { - f.updateLineNotTransmitted() - // Matching, not transmittable - return true - } - f.updateLineTransmitted() - - // Matching, transmittable - return false -} diff --git a/internal/io/fs/permissions/permission.go b/internal/io/fs/permissions/permission.go index cc5dd9b..d621c09 100644 --- a/internal/io/fs/permissions/permission.go +++ b/internal/io/fs/permissions/permission.go @@ -3,12 +3,12 @@ package permissions import ( - "github.com/mimecast/dtail/internal/io/logger" + "github.com/mimecast/dtail/internal/io/dlog" ) // ToRead is to check whether user has read permissions to a given file. func ToRead(user, filePath string) (bool, error) { // Only implemented for Linux, always expect true - logger.Warn(user, filePath, "Not performing ACL check, not supported on this platform") + dlog.Common.Debug(user, filePath, "Not performing ACL check as not compiled in") return true, nil } diff --git a/internal/io/fs/permissions/permission_linuxacl.go b/internal/io/fs/permissions/permission_linuxacl.go index 7d2d7ca..904b90f 100644 --- a/internal/io/fs/permissions/permission_linuxacl.go +++ b/internal/io/fs/permissions/permission_linuxacl.go @@ -13,7 +13,7 @@ import ( "unsafe" ) -// ToRead checks whether user has Linux file system permissions to read a given file. +// ToRead checks whether user has Linux file system permissions to read a file. func ToRead(user, filePath string) (bool, error) { cUser := C.CString(user) cFilePath := C.CString(filePath) diff --git a/internal/io/fs/readfile.go b/internal/io/fs/readfile.go index 161e3f0..90c5966 100644 --- a/internal/io/fs/readfile.go +++ b/internal/io/fs/readfile.go @@ -2,22 +2,35 @@ package fs import ( "bufio" + "bytes" "compress/gzip" "context" + "errors" "fmt" "io" "os" "strings" + "sync" "time" + "github.com/mimecast/dtail/internal/config" + "github.com/mimecast/dtail/internal/io/dlog" "github.com/mimecast/dtail/internal/io/line" - "github.com/mimecast/dtail/internal/io/logger" + "github.com/mimecast/dtail/internal/io/pool" "github.com/mimecast/dtail/internal/lcontext" "github.com/mimecast/dtail/internal/regex" "github.com/DataDog/zstd" ) +type readStatus int + +const ( + nothing readStatus = iota + abortReading readStatus = iota + continueReading readStatus = iota +) + // Used to tail and filter a local log file. type readFile struct { // Various statistics (e.g. regex hit percentage, transfer percentage). @@ -34,34 +47,14 @@ type readFile struct { canSkipLines bool // Seek to the EOF before processing file? seekEOF bool - limiter chan struct{} -} - -func (f readFile) makeReader(fd *os.File) (reader *bufio.Reader, err error) { - switch { - case strings.HasSuffix(f.FilePath(), ".gz"): - fallthrough - case strings.HasSuffix(f.FilePath(), ".gzip"): - logger.Info(f.FilePath(), "Detected gzip compression format") - var gzipReader *gzip.Reader - gzipReader, err = gzip.NewReader(fd) - if err != nil { - return - } - reader = bufio.NewReader(gzipReader) - case strings.HasSuffix(f.FilePath(), ".zst"): - logger.Info(f.FilePath(), "Detected zstd compression format") - reader = bufio.NewReader(zstd.NewReader(fd)) - default: - reader = bufio.NewReader(fd) - } - - return + // Warned already about a long line. + warnedAboutLongLine bool } // String returns the string representation of the readFile func (f readFile) String() string { - return fmt.Sprintf("readFile(filePath:%s,globID:%s,retry:%v,canSkipLines:%v,seekEOF:%v)", + return fmt.Sprintf( + "readFile(filePath:%s,globID:%s,retry:%v,canSkipLines:%v,seekEOF:%v)", f.filePath, f.globID, f.retry, @@ -80,141 +73,266 @@ func (f readFile) Retry() bool { } // Start tailing a log file. -func (f readFile) Start(ctx context.Context, lContext lcontext.LContext, lines chan<- line.Line, re regex.Regex) error { - logger.Debug("readFile", f) - defer func() { - select { - case <-f.limiter: - default: - } - }() +func (f readFile) Start(ctx context.Context, ltx lcontext.LContext, + lines chan<- *line.Line, re regex.Regex) error { - select { - case f.limiter <- struct{}{}: - default: - select { - case f.serverMessages <- logger.Warn(f.filePath, f.globID, "Server limit reached. Queuing file..."): - case <-ctx.Done(): - return nil - } - f.limiter <- struct{}{} + reader, fd, err := f.makeReader() + if fd != nil { + defer fd.Close() } - - fd, err := os.Open(f.filePath) if err != nil { return err } - defer fd.Close() - if f.seekEOF { - fd.Seek(0, io.SeekEnd) - } + rawLines := make(chan *bytes.Buffer, 100) + truncate := make(chan struct{}) - rawLines := make(chan []byte, 100) readCtx, readCancel := context.WithCancel(ctx) + var filterWg sync.WaitGroup + filterWg.Add(1) - filterDone := make(chan struct{}) + go f.periodicTruncateCheck(ctx, truncate) go func() { - f.filter(ctx, rawLines, lines, re, lContext) - close(filterDone) + f.filter(ctx, ltx, rawLines, lines, re) + filterWg.Done() // If the filter stopped, make the reader stop too, no need to read // more data if there is nothing more the filter wants to filter for! // E.g. it could be that we only want to filter N matches but not more. readCancel() }() - err = f.read(readCtx, fd, rawLines) + err = f.read(readCtx, fd, reader, rawLines, truncate) close(rawLines) - - // Filter may flushes some data still. So wait until it is done here. - <-filterDone + // Filter may sends some data still. So wait until it is done here. + filterWg.Wait() return err } -func (f readFile) read(ctx context.Context, fd *os.File, rawLines chan []byte) error { - var offset uint64 +func (f *readFile) makeReader() (*bufio.Reader, *os.File, error) { + if f.filePath == "" && f.globID == "-" { + return f.makePipeReader() + } + return f.makeFileReader() +} + +func (f *readFile) makeFileReader() (*bufio.Reader, *os.File, error) { + var reader *bufio.Reader + fd, err := os.Open(f.filePath) + if err != nil { + return reader, fd, err + } + + if f.seekEOF { + fd.Seek(0, io.SeekEnd) + } - reader, err := f.makeReader(fd) + reader, err = f.makeCompressedFileReader(fd) if err != nil { - return err + return reader, fd, err } - rawLine := make([]byte, 0, 512) - lineLengthThreshold := 1024 * 1024 // 1mb - longLineWarning := false + return reader, fd, nil +} - checkTruncate := f.truncateTimer(ctx) +func (f *readFile) makePipeReader() (*bufio.Reader, *os.File, error) { + return bufio.NewReader(os.Stdin), nil, nil +} +func (f *readFile) periodicTruncateCheck(ctx context.Context, truncate chan struct{}) { for { select { + case <-time.After(time.Second * 3): + select { + case truncate <- struct{}{}: + case <-ctx.Done(): + } case <-ctx.Done(): - return nil - default: + return } + } +} - select { - case <-checkTruncate: - if isTruncated, err := f.truncated(fd); isTruncated { - return err - } - logger.Info(f.filePath, "Current offset", offset) - default: +func (f *readFile) makeCompressedFileReader(fd *os.File) (reader *bufio.Reader, err error) { + switch { + case strings.HasSuffix(f.FilePath(), ".gz"): + fallthrough + case strings.HasSuffix(f.FilePath(), ".gzip"): + dlog.Common.Info(f.FilePath(), "Detected gzip compression format") + var gzipReader *gzip.Reader + gzipReader, err = gzip.NewReader(fd) + if err != nil { + return } + reader = bufio.NewReader(gzipReader) + case strings.HasSuffix(f.FilePath(), ".zst"): + dlog.Common.Info(f.FilePath(), "Detected zstd compression format") + reader = bufio.NewReader(zstd.NewReader(fd)) + default: + reader = bufio.NewReader(fd) + } + return +} + +func (f *readFile) read(ctx context.Context, fd *os.File, reader *bufio.Reader, + rawLines chan *bytes.Buffer, truncate <-chan struct{}) error { - // Read some bytes (max 4k at once as of go 1.12). isPrefix will - // be set if line does not fit into 4k buffer. - bytes, isPrefix, err := reader.ReadLine() + var offset uint64 + message := pool.BytesBuffer.Get().(*bytes.Buffer) + for { + b, err := reader.ReadByte() if err != nil { - // If EOF, sleep a couple of ms and return with nil error. - // If other error, return with non-nil error. - if err != io.EOF { + status, err := f.handleReadError(ctx, err, fd, rawLines, truncate, message) + if abortReading == status { return err } - if !f.seekEOF { - logger.Debug(f.FilePath(), "End of file reached") - return nil - } time.Sleep(time.Millisecond * 100) continue } - rawLine = append(rawLine, bytes...) - offset += uint64(len(bytes)) + offset++ + message.WriteByte(b) + + status, newMessage := f.handleReadByte(ctx, b, rawLines, message) + if status == abortReading { + return nil + } + message = newMessage + } +} + +// Filter log lines matching a given regular expression. +func (f *readFile) filter(ctx context.Context, ltx lcontext.LContext, + rawLines <-chan *bytes.Buffer, lines chan<- *line.Line, re regex.Regex) { + + // Do we have any kind of local context settings? If so then run the more complex + // filterWithLContext method. + if ltx.Has() { + // We can not skip transmitting any lines to the client with a local + // grep context specified. + f.canSkipLines = false + f.filterWithLContext(ctx, ltx, rawLines, lines, re) + return + } + + f.filterWithoutLContext(ctx, rawLines, lines, re) +} + +func (f *readFile) transmittable(rawLine *bytes.Buffer, length, capacity int, + re regex.Regex) (*line.Line, bool) { + + newLine := line.Null() + if !re.Match(rawLine.Bytes()) { + f.updateLineNotMatched() + f.updateLineNotTransmitted() + return newLine, false + } + f.updateLineMatched() + + // Can we actually send more messages, channel capacity reached? + if f.canSkipLines && length >= capacity { + f.updateLineNotTransmitted() + return newLine, false + } + f.updateLineTransmitted() + + return line.New(rawLine, f.totalLineCount(), f.transmittedPerc(), f.globID), true +} - if !isPrefix { - // last LineRead call returned contend until end of line. - rawLine = append(rawLine, '\n') +// Check wether log file is truncated. Returns nil if not. +func (f *readFile) truncated(fd *os.File) (bool, error) { + if fd == nil { + return false, nil + } + + dlog.Common.Debug(f.filePath, "File truncation check") + + // Can not seek currently open FD. + currentPosition, err := fd.Seek(0, os.SEEK_CUR) + if err != nil { + return true, err + } + // Can not open file at original path. + pathFd, err := os.Open(f.filePath) + if err != nil { + return true, err + } + defer pathFd.Close() + + // Can not seek file at original path. + pathPosition, err := pathFd.Seek(0, io.SeekEnd) + if err != nil { + return true, err + } + if currentPosition > pathPosition { + return true, errors.New("File got truncated") + } + return false, nil +} + +// Deal with the scenario that nothing could be read from the fd. +func (f *readFile) handleReadError(ctx context.Context, err error, fd *os.File, + rawLines chan *bytes.Buffer, truncate <-chan struct{}, + message *bytes.Buffer) (readStatus, error) { + + if err != io.EOF { + return abortReading, err + } + + select { + case <-truncate: + if isTruncated, err := f.truncated(fd); isTruncated { + return abortReading, err + } + case <-ctx.Done(): + return abortReading, nil + default: + } + + if !f.seekEOF { + dlog.Common.Info(f.FilePath(), "End of file reached") + if len(message.Bytes()) > 0 { select { - case rawLines <- rawLine: + case rawLines <- message: case <-ctx.Done(): - return nil - } - rawLine = make([]byte, 0, 512) - if longLineWarning { - longLineWarning = false } - continue } + return abortReading, nil + } - // Last LineRead call could not read content until end of line, buffer - // was too small. Determine whether we exceed the max line length we - // want dtail to send to the client at once. Possibly split up log line - // into multiple log lines. - if len(rawLine) >= lineLengthThreshold { - if !longLineWarning { - f.serverMessages <- logger.Warn(f.filePath, "Long log line, splitting into multiple lines") - // Only print out one warning per long log line. - longLineWarning = true + return nothing, nil +} + +// Now process the byte we just read from the fd. +func (f *readFile) handleReadByte(ctx context.Context, b byte, + rawLines chan *bytes.Buffer, message *bytes.Buffer) (readStatus, *bytes.Buffer) { + + switch b { + case '\n': + select { + case rawLines <- message: + message = pool.BytesBuffer.Get().(*bytes.Buffer) + f.warnedAboutLongLine = false + case <-ctx.Done(): + return abortReading, message + } + default: + if message.Len() >= config.Server.MaxLineLength { + if !f.warnedAboutLongLine { + f.serverMessages <- dlog.Common.Warn(f.filePath, + "Long log line, splitting into multiple lines") + f.warnedAboutLongLine = true } - rawLine = append(rawLine, '\n') + message.WriteByte('\n') select { - case rawLines <- rawLine: + case rawLines <- message: + message = pool.BytesBuffer.Get().(*bytes.Buffer) case <-ctx.Done(): - return nil + return abortReading, message } - rawLine = make([]byte, 0, 512) } } + + return nothing, message } diff --git a/internal/io/fs/readfilelcontext.go b/internal/io/fs/readfilelcontext.go new file mode 100644 index 0000000..44ce17d --- /dev/null +++ b/internal/io/fs/readfilelcontext.go @@ -0,0 +1,214 @@ +package fs + +import ( + "bytes" + "context" + + "github.com/mimecast/dtail/internal/io/line" + "github.com/mimecast/dtail/internal/io/pool" + "github.com/mimecast/dtail/internal/lcontext" + "github.com/mimecast/dtail/internal/regex" +) + +// The local context state. +type ltxState struct { + // Max state + maxCount int + processMaxCount bool + maxReached bool + + // Before state + before int + processBefore bool + beforeBuf chan *bytes.Buffer + + // After state + after int + processAfter bool +} + +// We don't have any local grep context, which makes life much simpler and more efficient. +func (f *readFile) filterWithoutLContext(ctx context.Context, rawLines <-chan *bytes.Buffer, + lines chan<- *line.Line, re regex.Regex) { + + for { + select { + case rawLine, ok := <-rawLines: + f.updatePosition() + if !ok { + return + } + if newLine, ok := f.transmittable(rawLine, len(lines), cap(lines), re); ok { + select { + case lines <- newLine: + case <-ctx.Done(): + return + } + } + } + } +} + +// Filter log lines matching a given regular expression, however with local grep context. +func (f *readFile) filterWithLContext(ctx context.Context, ltx lcontext.LContext, + rawLines <-chan *bytes.Buffer, lines chan<- *line.Line, re regex.Regex) { + + var ls ltxState + + // The following 3 scenarios may also be used at once/any combination together. + + // Scenario 1: Finish once maxCount hits found + ls.maxCount = ltx.MaxCount + ls.processMaxCount = ls.maxCount > 0 + ls.maxReached = false + + // Scenario 2: Print prev. N lines when current line matches. + ls.before = ltx.BeforeContext + ls.processBefore = ls.before > 0 + if ls.processBefore { + ls.beforeBuf = make(chan *bytes.Buffer, ls.before) + } + + // Screnario 3: Print next N lines when current line matches. + ls.after = 0 + ls.processAfter = ltx.AfterContext > 0 + + // No go through all raw lines read to determine with they satisfy the local + // context or not. "Matching" lines will be sent to the lines channel. + for rawLine := range rawLines { + status := f.filterLineWithLContext(ctx, <x, &ls, rawLines, lines, &re, rawLine) + switch status { + case abortReading: + return + default: + } + } +} + +// Filter log lines matching a given regular expression, however with local grep context. +func (f *readFile) filterLineWithLContext(ctx context.Context, ltx *lcontext.LContext, + ls *ltxState, rawLines <-chan *bytes.Buffer, lines chan<- *line.Line, re *regex.Regex, + rawLine *bytes.Buffer) readStatus { + + f.updatePosition() + + if !re.Match(rawLine.Bytes()) { + f.updateLineNotMatched() + status := f.lContextNotMatched(ctx, ls, lines, rawLine) + switch status { + case nothing: + default: + return status + } + } + + f.updateLineMatched() + + // If we have an "after" context to worry about... + if ls.processAfter { + if ls.maxReached { + // We have reached the "max" hits. Stop/abort reading. + return abortReading + } + // Reset the "after" context. + ls.after = ltx.AfterContext + } + + // If we have a "before" context to worry about... + if ls.processBefore { + status := f.lContextProcessBefore(ctx, ls, lines, rawLine) + switch status { + case nothing: + default: + return status + } + } + + line := line.New(rawLine, f.totalLineCount(), 100, f.globID) + + select { + case lines <- line: + // If we have a "max" context to worry about... + if ls.processMaxCount { + status := f.lContextProcessMaxCount(ctx, ls) + switch status { + case nothing: + default: + return status + } + } + case <-ctx.Done(): + return abortReading + } + + return nothing +} + +// Do some post-processing for the "after" and the "before" contexts in case the +// line didn't match the regex. +func (f *readFile) lContextNotMatched(ctx context.Context, ls *ltxState, + lines chan<- *line.Line, rawLine *bytes.Buffer) readStatus { + + if ls.processAfter && ls.after > 0 { + ls.after-- + myLine := line.New(rawLine, f.totalLineCount(), 100, f.globID) + + select { + case lines <- myLine: + case <-ctx.Done(): + return abortReading + } + + } else if ls.processBefore { + // Keep last num BeforeContext raw messages. + select { + case ls.beforeBuf <- rawLine: + default: + pool.RecycleBytesBuffer(<-ls.beforeBuf) + ls.beforeBuf <- rawLine + } + } + + return continueReading +} + +// Do some processing for the "before" context. +func (f *readFile) lContextProcessBefore(ctx context.Context, + ls *ltxState, lines chan<- *line.Line, rawLine *bytes.Buffer) readStatus { + + i := uint64(len(ls.beforeBuf)) + for { + select { + case rawLine := <-ls.beforeBuf: + myLine := line.New(rawLine, f.totalLineCount()-i, 100, f.globID) + i-- + + select { + case lines <- myLine: + case <-ctx.Done(): + return abortReading + } + default: + // beforeBuf is now empty. + } + if len(ls.beforeBuf) == 0 { + break + } + } + + return nothing +} + +// Do some processing for the "max" context. +func (f *readFile) lContextProcessMaxCount(ctx context.Context, ls *ltxState) readStatus { + ls.maxCount-- + if ls.maxCount == 0 { + if !ls.processAfter || ls.after == 0 { + return abortReading + } + // Unfortunatley we have to continue filter, as there might be more lines to print + ls.maxReached = true + } + + return nothing +} diff --git a/internal/io/fs/tailfile.go b/internal/io/fs/tailfile.go index 14994e5..7a40ac4 100644 --- a/internal/io/fs/tailfile.go +++ b/internal/io/fs/tailfile.go @@ -6,7 +6,7 @@ type TailFile struct { } // NewTailFile returns a new file tailer. -func NewTailFile(filePath string, globID string, serverMessages chan<- string, limiter chan struct{}) TailFile { +func NewTailFile(filePath string, globID string, serverMessages chan<- string) TailFile { return TailFile{ readFile: readFile{ filePath: filePath, @@ -15,7 +15,6 @@ func NewTailFile(filePath string, globID string, serverMessages chan<- string, l retry: true, canSkipLines: true, seekEOF: true, - limiter: limiter, }, } } diff --git a/internal/io/fs/truncate.go b/internal/io/fs/truncate.go deleted file mode 100644 index a8d59ac..0000000 --- a/internal/io/fs/truncate.go +++ /dev/null @@ -1,61 +0,0 @@ -package fs - -import ( - "context" - "errors" - "io" - "os" - "time" - - "github.com/mimecast/dtail/internal/io/logger" -) - -func (f readFile) truncateTimer(ctx context.Context) (checkTruncate chan struct{}) { - checkTruncate = make(chan struct{}) - - go func() { - for { - select { - case <-time.After(time.Second * 3): - select { - case checkTruncate <- struct{}{}: - case <-ctx.Done(): - } - case <-ctx.Done(): - return - } - } - }() - - return -} - -// Check wether log file is truncated. Returns nil if not. -func (f readFile) truncated(fd *os.File) (bool, error) { - logger.Debug(f.filePath, "File truncation check") - - // Can not seek currently open FD. - curPos, err := fd.Seek(0, os.SEEK_CUR) - if err != nil { - return true, err - } - - // Can not open file at original path. - pathFd, err := os.Open(f.filePath) - if err != nil { - return true, err - } - defer pathFd.Close() - - // Can not seek file at original path. - pathPos, err := pathFd.Seek(0, io.SeekEnd) - if err != nil { - return true, err - } - - if curPos > pathPos { - return true, errors.New("File got truncated") - } - - return false, nil -} |
