Tail a log file in the web browser 

Joined:
04/09/2007
Posts:
773

December 12, 2007 20:30:01    Last update: December 12, 2007 20:32:23
This is a script to tail a log file through the web browser. It uses AJAX, apache web server, mod_python, UNIX utilities tail (requires the --lines switch) and wc. The log file may reside on the web server or any other host accessible from the web server through SSH.

Although it's written in python, it should be easy to port to other languages such as Perl.

Apache httpd.conf:
LoadModule python_module modules/mod_python.so

# Log viewer
Alias /log_viewer "C:/work/log_viewer"
<Directory "C:/work/log_viewer">
    SetHandler mod_python
    PythonHandler mod_python.publisher
    DirectoryIndex index.py
    Options FollowSymlinks
    AllowOverride None
    Order deny,allow
    Allow from all
    Satisfy all
</Directory>


Python script:
import time, os
from os.path import basename

# command line utilities
SSH = 'C:/local/bin/plink.exe -C %(host)s -P %(port)s -l %(userid)s -pw %(passwd)s "%(cmd)s"'
LOCAL = '%(cmd)s'
CAT = 'cat %s'
WC = 'wc -l %s'
TAIL = 'wc -l %s && tail --lines=+%s %s'

# initial number of lines to show
INITIAL_LINES = 100

APPS_LIST = [ 
    'local_app',
    'remote_app', 
]

APPS = {
    'local_app' : {
        'name' : 'My Application - Local',
        'file' : 'C:/logs/myapp/mylog.%(date)s.txt',
        'shell' : LOCAL,
        'host' : 'localhost',
    },
    'remote_app' : { 
        'name' : 'My Application - Remote',
        'file' : '/home/myapp/mylog.%(date)s.txt',
        'shell' : SSH,
        'host' : 'apphost',
        'port' : 22,
        'userid' : 'userid',
        'passwd' : 'passwd',
     },
}

def index(app = None):
    appInfo = APPS.get(app)
    if appInfo:
        file = appInfo['file'] % { 'date' : time.strftime('%Y-%m-%d') }
    else:
        file = None

    return ''.join((html_header(app, file), html_footer()))

def download_log(req, app = None, file = None):
    appInfo = APPS.get(app)
    if not appInfo:
        return

    req.content_type = 'application/octet-stream'
    req.headers_out['Content-Disposition'] = 'attachment; filename=%s' % basename(file)
    appInfo['cmd'] =  CAT % file
    cmd =  appInfo['shell'] % appInfo 
    f = os.popen(cmd)
    for line in f:
        req.write(line)
    f.close()

def get_log(req, app = None, file = None, start_line = 1):
    try:
        start_line = int(start_line)
        if start_line < 1:
            start_line = 1
    except:
        start_line = 1
    (new_line, payload) = __get_log(app, file, start_line)
    req.content_type = 'text/xml'
    req.no_cache = True
    req.no_local_copy = True
    return """<?xml version="1.0" encoding="utf-8"?>
<ajax-response startLine="%s">
<![CDATA[%s]]>
</ajax-response>
""" % (new_line, payload)

def __get_log(app, file, start_line):
    appInfo = APPS.get(app)
    if not appInfo:
        return (1, '')

    if start_line == 1:
        appInfo['cmd'] = WC % file
        cmd = appInfo['shell'] % appInfo
        f = os.popen(cmd)
        lineCount = f.read()
        if f.close():
            return (1, '<span class="error">[ERROR] Failed to open file: %s:%s</span>' % (appInfo['host'], file))

        try:
            lineCount = int(lineCount.split()[0])
        except:
            return (1, '<span class="error">[ERROR] Failed to get a line count of: %s:%s (%s)</span>' % (appInfo['host'], file, lineCount))

        start_line = lineCount > INITIAL_LINES and (lineCount - INITIAL_LINES) or 1

    appInfo['cmd'] = TAIL % (file, start_line, file)
    cmd = appInfo['shell'] % appInfo

    f = os.popen(cmd)
    lineCount = f.readline()
    try:
        lineCount = int(lineCount.split()[0])
    except:
        return (1, '<span class="error">[ERROR] Failed to get a line count of: %s:%s (%s)</span>' % (appInfo['host'], file, lineCount))

    if start_line > lineCount:
        start_line = 1
    
    lines = []
    log_level = ''
    for line in f.readlines():
        if line.find('[DEBUG') >= 0:
            line = '<span class="debug">%s</span><br/>' % __escape(line.rstrip())
            log_level = 'DEBUG'
        elif line.find('[INFO') >= 0:
            line = '<span class="info">%s</span><br/>' % __escape(line.rstrip())
            log_level = 'INFO'
        elif line.find('[WARN') >= 0:
            line = '<span class="warn">%s</span><br/>' % __escape(line.rstrip())
            log_level = 'WARN'
        elif line.find('[ERROR') >= 0:
            line = '<span class="error">%s</span><br/>' % __escape(line.rstrip())
            log_level = 'ERROR'
        elif log_level == 'WARN' or log_level == 'ERROR':
            line = '<span class="%s">%s</span><br/>' % (log_level, __escape(line.rstrip()))
        else:
            line = '<span>%s</span><br/>' % __escape(line.rstrip())
        lines.append(line)
        start_line += 1

    if f.close():
        return (1, '<span class="error">[ERROR] Failed to read log file: %s:%s</span>' % (appInfo['host'], file))
    else:
        return (start_line, ''.join(lines))

def __escape(html):
    return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')

def html_header(theApp, file):
    # generate links
    links = []
    for app in APPS_LIST:
        links.append('<a href="./?app=%s">%s</a>' % (app, APPS[app]['name']))

    # set title, header and download link
    title = APPS.get(theApp) and APPS[theApp]['name'] or 'View Log Files'
    header = APPS.get(theApp) and APPS[theApp]['host'] + ': ' + file or ''
    downloadLink = APPS.get(theApp) and '<a href="download_log?app=%s&file=%s" title="Download"><img src="/download.gif" alt="Download" border="none"/></a>' % (theApp, file) or ''

    return """
<html>
<head>
<title>%s</title>
<style>
body {
    font-family: verdana, arial, sans-serif;
}

#loglinks {
    float: left;
    width: 100%%;
    margin-bottom: 20px;
}

#loglinks a {
    font-size: 0.85em;
    white-space: nowrap;
}

#log { color: #0000ff; }
#log .debug { color: #000000; }
#log .info { color: #208920; }
#log .warn { color: #9c20ee; }
#log .error { color: #ac2020; }

#pause-img {
    position: absolute;
    top: -100px;
    left: 0;
    cursor: pointer;
    z-index: 1;
}

#play-img {
    position: absolute;
    top: -100px;
    left: 0;
    cursor: pointer;
    z-index: 0;
}
</style>
<script language="JavaScript">
function ajaxGet(url, callback) {
    httpGet(url, function(xmldom) {
        var resp = xmldom.firstChild;
        while (resp) {
            if (resp.nodeName == 'ajax-response') {
                break;
            }
            resp = resp.nextSibling;
        }

        var retVal = new Object();
        if (resp) {
            var attributes = resp.attributes;
            for (var i = 0; i < attributes.length; i++) {
                var attr = attributes.item(i);
                retVal[attr.name] = attr.value;
            }

            var child = resp.firstChild;
            while (child) {
                if (child.nodeName == '#cdata-section') {
                    retVal.data = child.data;
                    break;
                }
                child = child.nextSibling;
            }
        }
        callback(retVal);
    });
}

function httpGet(url, callback) {
    var http = getXMLHTTP();
    http.open("GET", url, true);
    http.onreadystatechange = function() {
        if (http.readyState == 4) {
            var xml = http.responseXML;
            if (xml && xml.firstChild) {
                callback(xml);
            }
            else {
                callback(http.responseText);
            }
        }
    };

    http.send(null);
}

function getXMLHTTP() {
    var xmlhttp;
    try {
	xmlhttp = new XMLHttpRequest();
    }
    catch (e) {
	try {
	    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	}
	catch (e) {
	    throw "Unable to create an HTTP Request object";
	}
    }
    return xmlhttp;
}

var tail = true;
function refreshLog() {
    var theApp = document.forms[0].theApp;
    var theFile = document.forms[0].theFile;
    var startLine = document.forms[0].startLine;
    if (! tail) {
        window.setTimeout(refreshLog, 5000);
        return;
    }

    ajaxGet('get_log?app=' + theApp.value + '&file=' + theFile.value + '&start_line=' + startLine.value,
        function(resp) {
            if (resp.data && resp.data.length > 0) {
                startLine.value = resp.startLine;
                var newLines = document.createElement('DIV');
                newLines.innerHTML = resp.data;
                document.getElementById('log').appendChild(newLines);
                if (document.body.scrollHeight > document.body.clientHeight)
                {
                    document.body.scrollTop = document.body.scrollHeight - document.body.clientHeight;
                }

                var pause = document.getElementById("pause-img");
                var play = document.getElementById("play-img");
                pause.style.top = (document.body.scrollHeight - 22) + 'px';
                pause.style.left = (document.body.clientWidth - 22) + 'px';
                play.style.top = (document.body.scrollHeight - 22) + 'px';
                play.style.left = (document.body.clientWidth - 22) + 'px';
            }
            window.setTimeout(refreshLog, 5000);
        }
    );
}

function stopRefresh() {
    var pause = document.getElementById("pause-img");
    pause.style.display = 'none';
    tail = false;
}

function resumeRefresh() {
    var pause = document.getElementById("pause-img");
    pause.style.display = '';
    tail = true;
}

function handleScroll() {
    var pause = document.getElementById("pause-img");
    var play = document.getElementById("play-img");
    var left = document.body.clientWidth + document.body.scrollLeft - 22;
    pause.style.left = left + 'px';
    play.style.left = left + 'px';
}

window.onscroll = handleScroll;
</script>
</head>

<body onload="refreshLog();">
<div id="loglinks">%s</div>
<h3 id="header">%s %s</h3>
<form>
<input type="hidden" name="theApp" value="%s"/>
<input type="hidden" name="theFile" value="%s"/>
<input type="hidden" name="startLine" value="1"/>
</form>
<div>
<pre id="log">""" % (title, '&nbsp;|&nbsp;'.join(links), header, downloadLink, theApp, file)

def html_footer():
    return """</pre></div>
<img id="pause-img" src="/pause.gif" title="Pause" onclick="stopRefresh();"/>
<img id="play-img" src="/play.gif" title="Continue" onclick="resumeRefresh();"/>
</body></html>"""
Share |
| Comment  | Tags