// TailFile begins tailing the file. Output stream is made available // via the `Tail.Lines` channel. To handle errors during tailing, // invoke the `Wait` or `Err` method after finishing reading from the // `Lines` channel. func TailFile(filename string, config Config) (*Tail, error) { if config.ReOpen && !config.Follow { util.Fatal("cannot set ReOpen without Follow.") } t := &Tail{ Filename: filename, Lines: make(chan *Line), Config: config, } // when Logger was not specified in config, use default logger if t.Logger == nil { t.Logger = log.New(os.Stderr, "", log.LstdFlags) } if t.Poll { t.watcher = watch.NewPollingFileWatcher(filename) } else { t.watcher = watch.NewInotifyFileWatcher(filename) } if t.MustExist { var err error t.file, err = OpenFile(t.Filename) if err != nil { return nil, err } } go t.tailFileSync() return t, nil }
// run starts the goroutine in which the shared struct reads events from its // Watcher's Event channel and sends the events to the appropriate Tail. func (shared *InotifyTracker) run() { watcher, err := fsnotify.NewWatcher() if err != nil { util.Fatal("failed to create Watcher") } shared.watcher = watcher for { select { case winfo := <-shared.watch: shared.error <- shared.addWatch(winfo) case winfo := <-shared.remove: shared.removeWatch(winfo) case event, open := <-shared.watcher.Events: if !open { return } shared.sendEvent(event) case err, open := <-shared.watcher.Errors: if !open { return } else if err != nil { sysErr, ok := err.(*os.SyscallError) if !ok || sysErr.Err != syscall.EINTR { logger.Printf("Error in Watcher Error channel: %s", err) } } } } }
func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) *FileChanges { changes := NewFileChanges() var prevModTime time.Time // XXX: use tomb.Tomb to cleanly manage these goroutines. replace // the fatal (below) with tomb's Kill. fw.Size = pos origFi, err := os.Stat(fw.Filename) if err != nil { changes.NotifyDeleted() return changes } go func() { defer changes.Close() prevSize := fw.Size for { select { case <-t.Dying(): return default: } time.Sleep(POLL_DURATION) fi, err := os.Stat(fw.Filename) if err != nil { // Windows cannot delete a file if a handle is still open (tail keeps one open) // so it gives access denied to anything trying to read it until all handles are released. if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) { // File does not exist (has been deleted). changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } // File got moved/renamed? if !os.SameFile(origFi, fi) { changes.NotifyDeleted() return } // File got truncated? fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() prevSize = fw.Size continue } // File got bigger? if prevSize > 0 && prevSize < fw.Size { changes.NotifyModified() prevSize = fw.Size continue } prevSize = fw.Size // File was appended to (changed)? modTime := fi.ModTime() if modTime != prevModTime { prevModTime = modTime changes.NotifyModified() } } }() return changes }
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) *FileChanges { changes := NewFileChanges() err := Watch(fw.Filename) if err != nil { go changes.NotifyDeleted() } fw.Size = pos go func() { defer RemoveWatch(fw.Filename) defer changes.Close() events := Events(fw.Filename) for { prevSize := fw.Size var evt fsnotify.Event var ok bool select { case evt, ok = <-events: if !ok { return } case <-t.Dying(): return } switch { case evt.Op&fsnotify.Remove == fsnotify.Remove: fallthrough case evt.Op&fsnotify.Rename == fsnotify.Rename: changes.NotifyDeleted() return case evt.Op&fsnotify.Write == fsnotify.Write: fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() } else { changes.NotifyModified() } prevSize = fw.Size } } }() return changes }
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) { err := Watch(fw.Filename) if err != nil { return nil, err } changes := NewFileChanges() fw.Size = pos go func() { defer RemoveWatch(fw.Filename) events := Events(fw.Filename) for { prevSize := fw.Size var evt fsnotify.Event var ok bool select { case evt, ok = <-events: if !ok { return } case <-t.Dying(): return } switch { //With an open fd, unlink(fd) - inotify returns IN_ATTRIB (==fsnotify.Chmod) case evt.Op&fsnotify.Chmod == fsnotify.Chmod: if _, err := os.Stat(fw.Filename); err != nil { if !os.IsNotExist(err) { return } } fallthrough case evt.Op&fsnotify.Remove == fsnotify.Remove: fallthrough case evt.Op&fsnotify.Rename == fsnotify.Rename: changes.NotifyDeleted() return case evt.Op&fsnotify.Write == fsnotify.Write: fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() } else { changes.NotifyModified() } prevSize = fw.Size } } }() return changes, nil }
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, fi os.FileInfo) *FileChanges { changes := NewFileChanges() err := fw.w.Watch(fw.Filename) if err != nil { util.Fatal("Error watching %v: %v", fw.Filename, err) } fw.Size = fi.Size() go func() { defer fw.w.RemoveWatch(fw.Filename) defer changes.Close() for { prevSize := fw.Size var evt *fsnotify.FileEvent var ok bool select { case evt, ok = <-fw.w.Event: if !ok { return } case <-t.Dying(): return } switch { case evt.IsDelete(): fallthrough case evt.IsRename(): changes.NotifyDeleted() return case evt.IsModify(): fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() } else { changes.NotifyModified() } prevSize = fw.Size } } }() return changes }
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, fi os.FileInfo) *FileChanges { changes := NewFileChanges() if err := fw.w.Watch(fw.Filename); err != nil { util.Fatal("Error watching %v: %v", fw.Filename, err) } // Watch the directory to be notified when the file is deleted since the file // watch is on the inode, not the path. dirname := filepath.Dir(fw.Filename) if err := fw.w.WatchFlags(dirname, fsnotify.FSN_DELETE); err != nil { util.Fatal("Error watching %v: %v", dirname, err) } fw.Size = fi.Size() go func() { defer fw.w.RemoveWatch(fw.Filename) defer fw.w.RemoveWatch(dirname) defer changes.Close() for { prevSize := fw.Size var evt *fsnotify.FileEvent var ok bool select { case evt, ok = <-fw.w.Event: if !ok { return } case <-t.Dying(): return } switch { case evt.IsDelete(): if filepath.Base(evt.Name) != filepath.Base(fw.Filename) { continue } fallthrough case evt.IsRename(): changes.NotifyDeleted() return case evt.IsModify(): fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() } else { changes.NotifyModified() } prevSize = fw.Size } } }() return changes }