Saturday, October 2, 2010

Multiple file tail

This is my first attempt to post ode with some syntax highlighting.
I have chose GeSH for convenience and for the fact that I do not really enjoy much Javascript in my pages.
The tool does a "tail -f" on multiple files - like the output of a RollingFileAppender.

UPDATE: In the meanwhile I have found that Alex Gorbatchev's SyntaxHighlighter offers a hosting for it's brushes. In spite of my JS phobia I have chosen it over GeSH as the output looks way better.


#!/usr/bin/env python
import glob
import optparse
import os
import signal
import stat
import sys
import time
files = {}
def info(*text):
        if options.verbose==True:
                for token in text:
                        print token,
                print
       
def handler(sig, frame):
        if sig==signal.SIGUSR1:
                print "*** Caught USR1 ***"
                print "Watched files are: "
                for f in files.keys():
                        print f, " ",
                print
        if sig==signal.SIGINT:
                sys.exit(0)
       
class GenericLogFile:
        def __init__(self, name, label = False):
                self.name = name
                self.label = ""
                if label:
                        self.label=self.name+":"
                self.open()
       
        def close(self):
                self.file.close()
       
        def open(self):
                self.file = open(self.name, "r")
                self.file.seek(0, 2)
                self.lastpos = self.file.tell()
                self.mtime = os.stat(self.name)[stat.ST_MTIME]
                self.inode = os.stat(self.name)[stat.ST_INO]
       
        def reopen(self):
                self.open()
                self.file.seek(0, 0)
       
        def tell(self):
                return self.file.tell()
        def size(self):
                return os.stat(self.name)[stat.ST_SIZE]
               
        def changed(self):
                return os.stat(self.name)[stat.ST_INO]!=self.inode
        def tail(self):
                #Inode changed
                if self.changed():
                        info("***File ", self.name, " was changed")
                        self.close()
                        self.reopen()
               
                size = self.size()
                pos = self.tell()
               
                #File truncated
                if size<pos:
                        info("***File ", self.name, " was truncated(size=",size," pos=",pos,")")
                        self.close()
                        self.open()
                       
                #Data written since we have last checked
                if pos!=size:
                        if self.label=="":
                                info("***File ", self.name)
                        #Display all remaining lines
                        while True:
                                line = self.file.readline()
                                if line!="":
                                        print self.label+line,
                                else:
                                        break
       
               
help="""
DESCRIPTION
        This program works as tail -f for multiple files.
        files   -       any kind of file. They can be specified using shell patterns (*/?)
       
        -v              -       verbose output
        -l              -       label each line with the file ith has been read from
        -h              -       prints this help
       
WARNING
        Please enclose the filename patterns between single/double quotes in order to prevent
        shell globbing.
       
SIGNALS
        USR1    -       dumps a list of the open files
       
"""
def init():
        global options, args, parser, config
        #Initialization
        parser = optparse.OptionParser(help)
        parser.add_option("-l", "--label",  action="store_true", dest="label", help="label each line with the name of the file from that it was read")
        parser.add_option("-v", "--verbose",  action="store_true", dest="verbose", help="print more verbose diagnostics")
        (options, args) = parser.parse_args()
        signal.signal(signal.SIGUSR1, handler)
        signal.signal(signal.SIGINT, handler)
       
if __name__=='__main__':
        init()
        if len(args)!=0:
                lst = glob.glob(sys.argv[1])
                if len(lst)==0:
                        print "WARNING: No file matched the input yet!"
        else:
                parser.error("Incorrect number of arguments")
                parser.print_help()
               

#Initial situation
        #Get all the files already present
        for f in lst:
                if not f in files:
                        print "Added ", f
                        files[f] = GenericLogFile(f, options.label)
       
        #Main loop
        while True:
                lst = glob.glob(pattern)
               
                for f in files.keys():
                        if f not in lst:
                                #We no longer need this file...
                                info("Deleted ", f)
                                del files[f]
                                continue
                        else:
                                files[f].tail()
                       
                for f in lst:
                        if not f in files:
                                info("Added ", f)
                                #This is a file that appeared during program run.
                                #We have to print it all
                                files[f] = GenericLogFile(f, options.label)
                                files[f].reopen()
                                files[f].tail()
                time.sleep(1)

No comments:

Post a Comment