diff -r 40ccb8d3c96e -r 30c615bf7751 pvl/syslog/tail.py --- a/pvl/syslog/tail.py Fri Jan 04 14:19:05 2013 +0200 +++ b/pvl/syslog/tail.py Fri Jan 04 18:43:18 2013 +0200 @@ -1,52 +1,73 @@ """ - `tail -f` style continuous file reads. - - Checks if the file was renamed on EOF, and reopens if so. + Iterate over input lines in files. + + Can be read up to eof, on blocking inputs, or polled with a timeout (see: pvl.syslog.syslog.SyslogSource) """ import os -import logging; log = logging.getLogger('pvl.collectd.tail') +import logging; log = logging.getLogger('pvl.syslog.tail') -class TailFile (object) : +class Tail (object) : """ - A file on the filesystem, that is appended to. + Follow a file-like object, reading lines until EOF. + + Works with python file objects that buffer readlines() when using e.g. `tail -f ... | python -u ...`. """ - def __init__ (self, path, skip=None) : - self.path = path - self._file = None - self._id = None - - self._open() + def __init__ (self, file) : + self.file = file - if skip : - for line in self : - pass - - @property - def file (self) : + def readline (self) : """ - The underlying file objct, if opened. + Reads a line from the file, without trailing \n + + Raises EOF on end-of-file. """ - if self._file is None : - raise ValueError("I/O operation on closed file: {0}".format(self.path)) - - return self._file + line = self.file.readline() - @property - def fd (self) : - return self.file.fileno() + if not line : + raise EOFError() + else : + return line.rstrip('\n') - def fileno (self) : + def readlines (self) : """ - The underlying OS fd. + Reads any available lines from the file. """ - return self.fd + while True : + try : + line = self.readline() - def _stat (self) : + except EOFError : + log.debug("EOF") + break + + else : + yield line + + def skip (self) : + """ + Skip any available lines. + """ + + for line in self.readlines() : + pass + + __iter__ = readlines + +class TailFile (Tail) : + """ + Follow a file on the filesystem, reading lines until EOF, and re-opening if replaced. + """ + + def __init__ (self, path) : + self.path = path + self.reopen() + + def stat (cls) : """ Return a key identifying the file at our path. """ @@ -54,57 +75,20 @@ st = os.stat(self.path) return st.st_dev, st.st_ino - - def _open (self) : - assert self._file is None - - self._file = open(self.path, 'r') - self._id = self._stat() - - def _close (self) : - assert self._file is not None + + def open (cls) : + return open(self.path, 'r') - self._file.close() - self._file = None + def reopen (cls) : + self.file = self.open() + self._stat = self.stat() - def _reopen (self) : - """ - Re-open, in case the file changed.. - """ - - self._close() - self._open() - - def _changed (self) : + def changed (self) : """ Has the underlying file changed? """ - return self._stat() != self._id - - def poll (self, timeout) : - """ - XXX: not really implemented... - """ - - import time - - time.sleep(timeout) - - def readline (self) : - """ - Reads a line from the file. - - Raises EOF on end-of-file. - """ - - line = self.file.readline() - - # eof? - if not line : - raise EOFError() - - return line + return self.stat() != self._stat def readlines (self, eof_mark=False) : """ @@ -120,39 +104,22 @@ line = self.readline() except EOFError : - if self._changed() : - log.debug("EOF: file changed: reopen") - self._reopen() + if self.changed() : + log.debug("EOF: reopen") + self.reopen() if eof_mark : - # special token - yield None + yield None # special token + + # keep going + continue else : - log.debug("EOF: wait") - # done reading - return + log.debug("EOF") + break # wait else : - yield line.strip() + yield line __iter__ = readlines - def close (self) : - """ - Close the fifo. - """ - - if self._file is None : - raise ValueError("File already closed: {0}".format(self.path)) - - self._close() - - def __del__ (self) : - """ - Cleanup - """ - - if self._file is not None : - self._close() -