+#!/usr/bin/python3
+
+import sys, os, getopt, subprocess, time, select
+
+class host(object):
+ def __init__(self, hostname):
+ self.hostname = hostname
+ self.proc = None
+ self.lastconn = 0
+ self.outbuf = bytearray()
+ self.errbuf = bytearray()
+
+ def gotline(self, buf, line):
+ if buf == self.outbuf:
+ sys.stdout.buffer.write(line + b"\n")
+ sys.stdout.buffer.flush()
+ elif buf == self.errbuf:
+ sys.stderr.buffer.write(line + b"\n")
+ sys.stderr.buffer.flush()
+
+ def handle(self, fp, buf):
+ data = fp.read(4096)
+ if data == b"":
+ if len(self.outbuf) > 0:
+ self.gotline(self.outbuf, self.outbuf)
+ if len(self.errbuf) > 0:
+ self.gotline(self.errbuf, self.errbuf)
+ self.proc.wait()
+ self.proc = None
+ sys.stderr.write("loctail: disconnected from %s\n" % self.hostname)
+ buf.extend(data)
+ while True:
+ p = buf.find(b'\n')
+ if p < 0:
+ break
+ self.gotline(buf, buf[:p])
+ buf[:p + 1] = b""
+
+ def handleout(self):
+ self.handle(self.proc.stdout, self.outbuf)
+ def handleerr(self):
+ self.handle(self.proc.stderr, self.errbuf)
+
+ def connect(self):
+ self.proc = subprocess.Popen(["ssh", self.hostname, "tail", "-F", "/var/log/syslog"],
+ bufsize=0, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.lastconn = time.time()
+
+ def close(self):
+ if self.proc is not None:
+ self.proc.terminate()
+ self.proc.wait()
+ self.proc = None
+
+def min2(arg, *args):
+ ret = arg
+ for arg in args:
+ if ret is None or (arg is not None and arg < ret):
+ ret = arg
+ return ret
+
+def tailloop(hosts):
+ try:
+ now = time.time()
+ while True:
+ rfd = []
+ to = None
+ for host in hosts:
+ if host.proc is not None:
+ rfd.append(host.proc.stdout)
+ rfd.append(host.proc.stderr)
+ else:
+ to = min2(to, host.lastconn + 10)
+ rfd, wfd, efd = select.select(rfd, [], [], None if to is None else max(to - time.time(), 0))
+ now = time.time()
+ rfd = set(rfd)
+ for host in hosts:
+ if host.proc is not None:
+ if host.proc.stdout in rfd:
+ host.handleout()
+ elif host.proc.stderr in rfd:
+ host.handleerr()
+ else:
+ if now > host.lastconn + 10:
+ host.connect()
+ except KeyboardInterrupt:
+ for host in hosts:
+ host.close()
+
+def usage(out):
+ out.write("usage: logtail [-h] HOST...\n")
+
+opts, args = getopt.getopt(sys.argv[1:], "h")
+for o, a in opts:
+ if o == "-h":
+ usage(sys.stdout)
+ sys.exit(0)
+if len(args) < 1:
+ usage(sys.stderr)
+ sys.exit(1)
+
+hosts = [host(arg) for arg in args]
+tailloop(hosts)