--- 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()
-