Added logtail.
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 7 May 2017 00:33:11 +0000 (02:33 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 7 May 2017 00:33:11 +0000 (02:33 +0200)
logtail [new file with mode: 0755]

diff --git a/logtail b/logtail
new file mode 100755 (executable)
index 0000000..fc93fb7
--- /dev/null
+++ b/logtail
@@ -0,0 +1,103 @@
+#!/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)