Tail.py

From PeformIQ Upgrade
Jump to navigation Jump to search

Script

#!/usr/bin/python

'''A module which implements a unix-like "tail" of a file.
A callback is made for every new line found in the file.  Options
specify whether the existing contents of the file should be read
or ignored.

@author Sean Reifschneider <jafo@tummy.com>
@version $Revision: 1.51 $

If a file is emptied or removed, the tail will continue reading lines
which are written in the new place.

Simple example:

	import tail

	def callback(line):
		print 'Line: "%s"' % string.rstrip(line)

	tail.tail('/var/log/all', callback).mainloop()
'''

import time
import string
import os

class tail:
	def __init__(self, filename, callback, tailbytes = 0):
		'''Create a new tail instance.
		Create a tail object which periodicly polls the specified file looking
		for new data which was written.  The callback routine is called for each
		new line found in the file.

		@return Nothing
		@param filename File to read.
		@param callback Function which takes one argument, called with each
				line read from the file.
		@param tailbytes Specifies bytes from end of file to start reading
				(defaults to 0, meaning skip entire file, -1 means read full file).
		'''
		self.skip = tailbytes
		self.filename = filename
		self.callback = callback
		self.fp = None
		self.lastSize = 0
		self.lastInode = -1
		self.data = ''

	def process(self):
		'''Examine file looking for new lines.
		When called, this function will process all lines in the file being
		tailed, detect the original file being renamed or reopened, etc...
		This should be called periodicly to look for activity on the file.

		@return Nothing
		'''
		#  open file if it's not already open
		if not self.fp:
			try:
				self.fp = open(self.filename, 'r')
				stat = os.stat(self.filename)
				self.lastIno = stat[1]
				if self.skip >= 0 and stat[6] > self.skip:
					self.fp.seek(0 - (self.skip), 2)
				self.skip = -1
				self.lastSize = 0
			except:
				if self.fp: self.fp.close()
				self.skip = -1    #  if the file doesn't exist, we don't skip
				self.fp = None
		if not self.fp: return

		#  check to see if file has moved under us
		try:
			stat = os.stat(self.filename)
			thisSize = stat[6]
			thisIno = stat[1]
			if thisSize < self.lastSize or thisIno != self.lastIno:
				raise Exception
		except:
			self.fp.close()
			self.fp = None
			self.data = ''
			return

		#  read if size has changed
		if self.lastSize < thisSize:
			while 1:
				thisData = self.fp.read(4096)
				if len(thisData) < 1:
					break
				self.data = self.data + thisData

				#  process lines within the data
				while 1:
					pos = string.find(self.data, '\n')
					if pos < 0: break
					line = self.data[:pos]
					self.data = self.data[pos + 1:]
					#  line is line read from file
					if self.callback: self.callback(line)

		self.lastSize = thisSize
		self.lastIno = thisIno

	def mainloop(self, sleepfor = 5):
		'''Loop forever processing activity on the tail object.
		This routine is intended to be called in programs which do not need
		to do other processing.  This routine never returns.

		@return Never returns
		@param sleepfor Seconds between processing (default is 5 seconds).
		'''
		while 1:
			self.process()
			time.sleep(sleepfor)

if __name__ == "__main__":
	def callback(line):
		print 'Line: "%s"' % string.rstrip(line)

	tail('/var/log/all', callback).mainloop()