Skip to content

Instantly share code, notes, and snippets.

@fiorix
Created February 27, 2012 00:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save fiorix/1920022 to your computer and use it in GitHub Desktop.
Save fiorix/1920022 to your computer and use it in GitHub Desktop.
tail -F and broadcast to all of your browsers via sse
#!/usr/bin/env python
# coding: utf-8
#
# start the server:
# python webtail.py /var/log/syslog
#
# point all your browsers to:
# http://localhost:8888
import os
import stat
import sys
from cyclone.bottle import run, route
from cyclone.sse import SSEHandler
from twisted.internet import reactor
from twisted.internet import task
class LogProtocolError(Exception):
pass
class LogProtocol(object):
poll_interval = 0.5
def __init__(self):
self.fd = None
def follow(self, filename):
if self.fd is None:
self.openFile(filename, tail=True)
else:
raise LogProtocolError("Already following %s" % self.filename)
def stop(self):
if self.fd is None:
raise LogProtocolError("Not started.")
else:
self.task.stop()
self.fd.close()
self.fd = None
def openFile(self, filename, tail=False):
self.filename = filename
self.fd = open(filename)
self.fstat = os.fstat(self.fd.fileno())
if tail:
self.fd.seek(0, 2)
self.task = task.LoopingCall(self.readLine)
self.task.start(self.poll_interval)
def file_identity(self, struct_stat):
return struct_stat[stat.ST_DEV], struct_stat[stat.ST_INO]
def readLine(self):
while True:
line = self.fd.readline()
if line:
self.lineReceived(line)
else:
break
try:
st = os.stat(self.filename)
except:
st = self.fstat
if self.file_identity(st) != self.file_identity(self.fstat):
self.stop()
self.follow(self.filename)
def lineReceived(self, line):
pass
@route("/")
def index(self):
self.write("""
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
font-family: sans-serif;
background-color: black;
font-family: courier;
font-size: 12px;
color: white;
}
</style>
</head>
<body>
<div id="console"></div>
<script>
var source = new EventSource('/live');
source.onmessage = function(m) {
document.getElementById("console").innerHTML += m.data;
window.scrollBy(0, 50);
}
</script>
</body>
</html>
""")
class LogMixin(object):
waiters = []
def subscribe(self, client):
LogMixin.waiters.append(client)
def unsubscribe(self, client):
LogMixin.waiters.remove(client)
def broadcast(self, message):
message = message.replace("\n", "<br>")
for client in LogMixin.waiters:
client.sendEvent(message)
class LogFile(LogProtocol, LogMixin):
def lineReceived(self, line):
self.broadcast(line)
class LiveHandler(SSEHandler, LogMixin):
def bind(self):
self.subscribe(self)
def unbind(self):
self.unsubscribe(self)
def main():
try:
filename = sys.argv[1]
except:
print "use: %s file.log" % sys.argv[0]
sys.exit(1)
log = LogFile()
log.follow(filename)
run(host="127.0.0.1",
port=8888,
debug=True,
log=sys.stdout,
more_handlers=[
(r"/live", LiveHandler),
])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment