terom@31: """ terom@31: `tail -f` style continuous file reads. terom@31: terom@31: Checks if the file was renamed on EOF, and reopens if so. terom@31: """ terom@31: terom@31: import os terom@31: terom@31: import logging; log = logging.getLogger('pvl.collectd.tail') terom@31: terom@31: class TailFile (object) : terom@31: """ terom@31: A file on the filesystem, that is appended to. terom@31: """ terom@31: terom@31: def __init__ (self, path) : terom@31: self.path = path terom@31: self._file = None terom@31: self._id = None terom@31: terom@31: self._open() terom@31: terom@31: @property terom@31: def file (self) : terom@31: """ terom@31: The underlying file objct, if opened. terom@31: """ terom@31: terom@31: if self._file is None : terom@31: raise ValueError("I/O operation on closed file: {0}".format(self.path)) terom@31: terom@31: return self._file terom@31: terom@31: @property terom@31: def fd (self) : terom@31: return self.file.fileno() terom@31: terom@31: def fileno (self) : terom@31: """ terom@31: The underlying OS fd. terom@31: """ terom@31: terom@31: return self.fd terom@31: terom@31: def _stat (self) : terom@31: """ terom@31: Return a key identifying the file at our path. terom@31: """ terom@31: terom@31: st = os.stat(self.path) terom@31: terom@31: return st.st_dev, st.st_ino terom@31: terom@31: def _open (self) : terom@31: assert self._file is None terom@31: terom@31: self._file = open(self.path, 'r') terom@31: self._id = self._stat() terom@31: terom@31: def _close (self) : terom@31: assert self._file is not None terom@31: terom@31: self._file.close() terom@31: self._file = None terom@31: terom@31: def _reopen (self) : terom@31: """ terom@31: Re-open, in case the file changed.. terom@31: """ terom@31: terom@31: self._close() terom@31: self._open() terom@31: terom@31: def _changed (self) : terom@31: """ terom@31: Has the underlying file changed? terom@31: """ terom@31: terom@31: return self._stat() != self._id terom@31: terom@31: def poll (self, timeout) : terom@31: """ terom@31: XXX: not really implemented... terom@31: """ terom@31: terom@31: import time terom@31: terom@31: time.sleep(timeout) terom@31: terom@31: def readline (self) : terom@31: """ terom@31: Reads a line from the file. terom@31: terom@31: Raises EOF on end-of-file. terom@31: """ terom@31: terom@31: line = self.file.readline() terom@31: terom@31: # eof? terom@31: if not line : terom@31: raise EOFError() terom@31: terom@31: return line terom@31: terom@31: def readlines (self, eof_mark=False) : terom@31: """ terom@31: Reads any available lines from the file. terom@31: terom@31: Reopens the file on EOF if the underlying file changed. terom@31: terom@31: eof_mark: yields a special None line when this happens. terom@31: """ terom@31: terom@31: while True : terom@31: try : terom@31: line = self.readline() terom@31: terom@31: except EOFError : terom@31: if self._changed() : terom@31: log.debug("EOF: file changed: reopen") terom@31: self._reopen() terom@31: terom@31: if eof_mark : terom@31: # special token terom@31: yield None terom@31: terom@31: else : terom@31: log.debug("EOF: wait") terom@31: # done reading terom@31: return terom@31: terom@31: else : terom@31: yield line.strip() terom@31: terom@31: __iter__ = readlines terom@31: terom@31: def close (self) : terom@31: """ terom@31: Close the fifo. terom@31: """ terom@31: terom@31: if self._file is None : terom@31: raise ValueError("File already closed: {0}".format(self.path)) terom@31: terom@31: self._close() terom@31: terom@31: def __del__ (self) : terom@31: """ terom@31: Cleanup terom@31: """ terom@31: terom@31: if self._file is not None : terom@31: self._close() terom@31: