Commit | Line | Data |
---|---|---|
8828f477 FT |
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) |