Source code for duct.logs.follower

"""
.. module:: follower
   :synopsis: Log tailing classes

.. moduleauthor:: Colin Alston <colin@tamvera.com>
"""
import os


[docs] class LogFollower(object): """Provides a class for following log files between runs :param logfile: Full path to logfile :type logfile: str :param parser: Optional parser method for log lines :type parser: str :param tmp_path: Temporary path used to store position markers :type tmp_path: str :param history: If True the log is read from the start instead of tailed unless its already been read :type history: str """ def __init__(self, logfile, parser=None, tmp_path="/var/lib/duct/", history=False): self.logfile = logfile self.tmp = os.path.join( tmp_path, f"{self.logfile.lstrip('/').replace('/', '-')}.lf" ) self.history = history self.lastInode = None self.lastSize = -1 self.readLast() self.parser = parser
[docs] def cleanStore(self): """Clear up the log position store """ os.unlink(self.tmp)
[docs] def storeLast(self): """Persist the current position in the file """ with open(self.tmp, 'wt', encoding='utf-8') as fi: fi.write(f'{self.lastSize}:{self.lastInode}')
[docs] def readLast(self): """Read the latest changes in the file """ if os.path.exists(self.tmp): with open(self.tmp, 'rt', encoding='utf-8') as fi: ls, li = fi.read().split(':') self.lastSize = int(ls) self.lastInode = int(li) else: if self.history: self.lastSize = 0 self.lastInode = 0 else: # Don't re-read the entire file stat = os.stat(self.logfile) self.lastSize = stat.st_size self.lastInode = stat.st_ino
[docs] def get_fn(self, fn, max_lines=None): """Passes each parsed log line to `fn` This is a better idea than storing a giant log file in memory """ stat = os.stat(self.logfile) if (stat.st_ino == self.lastInode) and (stat.st_size == self.lastSize): # Nothing new return [] # Handle rollover and rotations vaguely if (stat.st_ino != self.lastInode) or (stat.st_size < self.lastSize): self.lastSize = 0 fi = open(self.logfile, 'rt', encoding='utf-8') # pylint: disable=consider-using-with fi.seek(self.lastSize) self.lastInode = stat.st_ino lines = 0 for i in fi: lines += 1 if max_lines and (lines > max_lines): self.storeLast() fi.close() return if '\n' in i: self.lastSize += len(i) if self.parser: line = self.parser(i.strip('\n')) else: line = i.strip('\n') fn(line) self.storeLast() fi.close()
[docs] def get(self, max_lines=None): """Returns a big list of all log lines since the last run """ rows = [] self.get_fn(rows.append, max_lines=max_lines) return rows