Privacy Policy
Snippets index

  Tail multiple logfiles in python

#!/usr/bin/env python3

import os
import sys
import time
import glob

# Adapted from:
# https://stackoverflow.com/questions/5725051/tail-multiple-logfiles-in-python#5749497

def print_file_from(filename, pos):
    with open(filename, 'rb') as fh:
        fh.seek(pos)
        while True:
            chunk = fh.read(8192)
            if not chunk:
                break
            sys.stdout.write(chunk.decode())


def _fstat(filename):
    st_results = os.stat(filename)
    return (st_results[6], st_results[8])


def _print_if_needed(filename, last_stats, no_fn, last_fn):
    changed = False
    #Find the size of the file and move to  the end
    tup = _fstat(filename)
    # print tup
    if last_stats[filename] != tup:
        changed = True
        if not no_fn and last_fn != filename:
            print('\n<%s>' % filename)
        print_file_from(filename, last_stats[filename][0])
        last_stats[filename] = tup
    return changed


def multi_tail(filenames, stdout=sys.stdout, interval=1, idle=10, no_fn=False):
    #S = lambda (st_size, st_mtime): (max(0, st_size - 124), st_mtime)
    def S(st_size, st_mtime):
        return max(0, st_size - 124), st_mtime

    last_stats = dict((fn, S(*_fstat(fn))) for fn in filenames)
    last_fn = None
    last_print = 0
    while 1:
        # print last_stats
        changed = False
        for filename in filenames:
            if _print_if_needed(filename, last_stats, no_fn, last_fn):
                changed = True
                last_fn = filename
        if changed:
            if idle > 0:
                last_print = time.time()
        else:
            if idle > 0 and last_print is not None:
                if time.time() - last_print >= idle:
                    last_print = None
                    print('\n' * 4)
            time.sleep(interval)


if '__main__' == __name__:
    from optparse import OptionParser
    op = OptionParser()
    #op.add_option('-F', '--no-fn', help="don't print filename when changes", default=False, action='store_true')
    op.add_option('-f', '--print-filename', help="print filename when changes", default=False, action='store_true')
    op.add_option('-i', '--idle', help='idle time, in seconds (0 turns off)', type='int', default=10)
    op.add_option('--interval', help='check interval, in seconds', type='int', default=1)
    opts, args = op.parse_args()

    # Todo: add icon based on log level
    # print("📕: error message")
    # print("📙: warning message")
    # print("📗: ok status message")
    # print("📘: action message")
    # print("📓: canceled status message")
    # print("📔: Or anything you like and want to recognize immediately by color")

    # Use glob to expand filenames on Windows shell
    filenames = []
    for arg in args:
        filenames += glob.glob(arg)

    try:
        #multi_tail(args, interval=opts.interval, idle=opts.idle, no_fn=opts.no_fn)
        multi_tail(filenames, interval=opts.interval, idle=opts.idle, no_fn=not opts.print_filename)
    except KeyboardInterrupt:
        pass