From 407035634f5da72a54feb2ac7120dab583980451 Mon Sep 17 00:00:00 2001 From: Nakul <1407686+theencee@users.noreply.github.com> Date: Tue, 31 Aug 2021 15:49:19 -0700 Subject: [PATCH] Any file changes that occurred before InotifyFileWatcher was started but after the preceding EOF would not be detected (#1) --- util/util.go | 9 +++++++-- watch/inotify.go | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/util/util.go b/util/util.go index b64caa2..9c466c7 100644 --- a/util/util.go +++ b/util/util.go @@ -17,14 +17,19 @@ type Logger struct { var LOGGER = &Logger{log.New(os.Stderr, "", log.LstdFlags)} -// fatal is like panic except it displays only the current goroutine's stack. +// Fatal is like panic except it displays only the current goroutine's stack. func Fatal(format string, v ...interface{}) { // https://github.com/nxadm/log/blob/master/log.go#L45 LOGGER.Output(2, fmt.Sprintf("FATAL -- "+format, v...)+"\n"+string(debug.Stack())) os.Exit(1) } -// partitionString partitions the string into chunks of given size, +// Error logs an error message with the current goroutine stack and returns. It doesn't quit. +func Error(format string, v ...interface{}) { + LOGGER.Output(2, fmt.Sprintf("ERROR -- "+format, v...)+"\n"+string(debug.Stack())) +} + +// PartitionString partitions the string into chunks of given size, // with the last chunk of variable size. func PartitionString(s string, chunkSize int) []string { if chunkSize <= 0 { diff --git a/watch/inotify.go b/watch/inotify.go index cbd11ad..6ffab80 100644 --- a/watch/inotify.go +++ b/watch/inotify.go @@ -11,7 +11,7 @@ import ( "github.com/nxadm/tail/util" - "github.com/fsnotify/fsnotify" + "github.com/fsnotify/fsnotify" "gopkg.in/tomb.v1" ) @@ -79,6 +79,13 @@ func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChange events := Events(fw.Filename) + // Check to see the file hasn't been modified already before the watcher started + fi, deleted := fw.checkAndNotifyIfModifiedInBetween(changes) + if deleted { + return + } + fw.Size = fi.Size() + for { prevSize := fw.Size @@ -134,3 +141,28 @@ func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChange return changes, nil } + +func (fw *InotifyFileWatcher) checkAndNotifyIfModifiedInBetween(changes *FileChanges) (os.FileInfo, bool) { + fi, err := os.Stat(fw.Filename) + if err != nil { + if !os.IsNotExist(err) { + util.Error("Failed to stat file %v: %v", fw.Filename, err) + // Treat it as a deleted file as we cannot read it anyway. + } + _ = RemoveWatch(fw.Filename) + changes.NotifyDeleted() + return nil, true + } + + if fw.Size > fi.Size() { // old file size was larger than now => truncated + changes.NotifyTruncated() + } else if fw.Size != fi.Size() { + changes.NotifyModified() + } + // there is a corner case of file that was truncated and replaced with exact same amount of bytes, which would + // result in no notification. However any subsequent writes will capture that + // If the file isn't expected to be written to often and those events cannot be missed, recommend using Polling watcher + // instead of inotify + + return fi, false +}