Added logtail.
[utils.git] / logtail
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)