pvl/syslog/tail.py
author Tero Marttila <terom@paivola.fi>
Thu, 10 Jan 2013 17:50:10 +0200
changeset 73 ef01c4639689
parent 59 caed0ed82709
child 114 2e88e1d8e604
permissions -rw-r--r--
pvl.syslog.tail: log rstrip'd line
"""
    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.syslog.tail')

class Tail (object) :
    """
        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, file, skip=None) :
        self.file = file
        
        if skip :
            self.skip()

    def readline (self) :
        """
            Reads a line from the file, without trailing \n

            Raises EOF on end-of-file.
        """

        line = self.file.readline()

        if not line :
            raise EOFError()
        
        line = line.rstrip('\n')

        log.debug("%s", line)

        return line

    def readlines (self) :
        """
            Reads any available lines from the file.
        """

        while True :
            try :
                line = self.readline()

            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, **opts) :
        log.debug("%s", path)

        self.path = path
        self._stat = self.stat()

        file = self.open()
        
        # may call skip -> readlines -> changed() -> _stat
        Tail.__init__(self, file, **opts)
    
    def stat (self) :
        """
            Return a key identifying the file at our path.
        """

        st = os.stat(self.path)

        stat = st.st_dev, st.st_ino

        log.debug("%s: %s", self, stat)

        return stat
    
    def open (self) :
        log.debug("%s", self)
        return open(self.path, 'r')

    def reopen (self) :
        self.file = self.open()
        self._stat = self.stat()

    def changed (self) :
        """
            Has the underlying file changed?
        """

        return self.stat() != self._stat

    def readlines (self, eof_mark=False) :
        """
            Reads any available lines from the file.

            Reopens the file on EOF if the underlying file changed.

                eof_mark:   yields a special None line when this happens.
        """

        while True :
            try :
                line = self.readline()

            except EOFError :
                if self.changed() :
                    log.debug("EOF: reopen")
                    self.reopen()
                    
                    if eof_mark :
                        yield None # special token
                    
                    # keep going
                    continue

                else :
                    log.debug("EOF")
                    break # wait

            else :
                yield line

    __iter__ = readlines

    def __str__ (self) :
        return self.path