| 1 | #!/usr/bin/python3 |
| 2 | |
| 3 | import sys, os, getopt, subprocess, time, select |
| 4 | |
| 5 | class host(object): |
| 6 | def __init__(self, hostname): |
| 7 | self.hostname = hostname |
| 8 | self.proc = None |
| 9 | self.lastconn = 0 |
| 10 | self.outbuf = bytearray() |
| 11 | self.errbuf = bytearray() |
| 12 | |
| 13 | def gotline(self, buf, line): |
| 14 | if buf == self.outbuf: |
| 15 | sys.stdout.buffer.write(line + b"\n") |
| 16 | sys.stdout.buffer.flush() |
| 17 | elif buf == self.errbuf: |
| 18 | sys.stderr.buffer.write(line + b"\n") |
| 19 | sys.stderr.buffer.flush() |
| 20 | |
| 21 | def handle(self, fp, buf): |
| 22 | data = fp.read(4096) |
| 23 | if data == b"": |
| 24 | if len(self.outbuf) > 0: |
| 25 | self.gotline(self.outbuf, self.outbuf) |
| 26 | if len(self.errbuf) > 0: |
| 27 | self.gotline(self.errbuf, self.errbuf) |
| 28 | self.proc.wait() |
| 29 | self.proc = None |
| 30 | sys.stderr.write("loctail: disconnected from %s\n" % self.hostname) |
| 31 | buf.extend(data) |
| 32 | while True: |
| 33 | p = buf.find(b'\n') |
| 34 | if p < 0: |
| 35 | break |
| 36 | self.gotline(buf, buf[:p]) |
| 37 | buf[:p + 1] = b"" |
| 38 | |
| 39 | def handleout(self): |
| 40 | self.handle(self.proc.stdout, self.outbuf) |
| 41 | def handleerr(self): |
| 42 | self.handle(self.proc.stderr, self.errbuf) |
| 43 | |
| 44 | def connect(self): |
| 45 | self.proc = subprocess.Popen(["ssh", self.hostname, "tail", "-F", "/var/log/syslog"], |
| 46 | bufsize=0, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 47 | self.lastconn = time.time() |
| 48 | |
| 49 | def close(self): |
| 50 | if self.proc is not None: |
| 51 | self.proc.terminate() |
| 52 | self.proc.wait() |
| 53 | self.proc = None |
| 54 | |
| 55 | def min2(arg, *args): |
| 56 | ret = arg |
| 57 | for arg in args: |
| 58 | if ret is None or (arg is not None and arg < ret): |
| 59 | ret = arg |
| 60 | return ret |
| 61 | |
| 62 | def tailloop(hosts): |
| 63 | try: |
| 64 | now = time.time() |
| 65 | while True: |
| 66 | rfd = [] |
| 67 | to = None |
| 68 | for host in hosts: |
| 69 | if host.proc is not None: |
| 70 | rfd.append(host.proc.stdout) |
| 71 | rfd.append(host.proc.stderr) |
| 72 | else: |
| 73 | to = min2(to, host.lastconn + 10) |
| 74 | rfd, wfd, efd = select.select(rfd, [], [], None if to is None else max(to - time.time(), 0)) |
| 75 | now = time.time() |
| 76 | rfd = set(rfd) |
| 77 | for host in hosts: |
| 78 | if host.proc is not None: |
| 79 | if host.proc.stdout in rfd: |
| 80 | host.handleout() |
| 81 | elif host.proc.stderr in rfd: |
| 82 | host.handleerr() |
| 83 | else: |
| 84 | if now > host.lastconn + 10: |
| 85 | host.connect() |
| 86 | except KeyboardInterrupt: |
| 87 | for host in hosts: |
| 88 | host.close() |
| 89 | |
| 90 | def usage(out): |
| 91 | out.write("usage: logtail [-h] HOST...\n") |
| 92 | |
| 93 | opts, args = getopt.getopt(sys.argv[1:], "h") |
| 94 | for o, a in opts: |
| 95 | if o == "-h": |
| 96 | usage(sys.stdout) |
| 97 | sys.exit(0) |
| 98 | if len(args) < 1: |
| 99 | usage(sys.stderr) |
| 100 | sys.exit(1) |
| 101 | |
| 102 | hosts = [host(arg) for arg in args] |
| 103 | tailloop(hosts) |