root/livinglogic.python.tipimaid/liaalh.py @ 38:db0987cbb72d

Revision 38:db0987cbb72d, 5.3 KB (checked in by Nikolas Tautenhahn <nik@…>, 10 years ago)

create more output when things are not-so-good

  • Property exe set to *
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Apache sends a SIGTERM to its piped logger when it exits - when apache is killed with -9, it doesn't send anything
5
6import sys, os, datetime, errno, bisect, re, gzip, signal, time
7
8
9class LogLine(tuple):
10    def __lt__(self, other):
11        return self[0] < other[0]
12
13    def __le__(self, other):
14        return self[0] <= other[0]
15
16
17class Buffer(object):
18    def __init__(self, pattern='', gzip_logs=None, buffertime=0, stream=sys.stdin, utcrotate=False):
19        self.pattern = pattern
20        self.gzip_logs = gzip_logs
21        self.data = []
22        self.servers = {}
23        self.buffertime = datetime.timedelta(seconds=buffertime)
24        self.stream = stream
25        self.re_find_date = re.compile(" \[(.*?)\] ")
26        if gzip_logs is not None and not pattern.endswith(".gz"):
27            self.pattern = "%s.gz" % self.pattern
28        self.utcrotate = utcrotate
29        self.updateutcoffset()
30        self.handlevirtualhost = "%v" in pattern
31        if buffertime > 0:
32            self.run = self.run_buffered
33        else:
34            self.run = self.run_unbuffered
35
36    def openfile(self, filename):
37        try:
38            f = open(filename, "a", 1)
39        except IOError, exc:
40            if exc.errno == errno.ENOENT:
41                os.makedirs(os.path.dirname(filename))
42                f = open(filename, "a", 1)
43            else:
44                raise
45        if self.gzip_logs is not None:
46            f = gzip.GzipFile(fileobj=f, compresslevel=self.gzip_logs)
47            f.name = f.fileobj.name # gzip-fileobjects don't have a name attribute
48        return f
49
50    def readlines(self):
51        while True:
52            try:
53                line = self.stream.readline()
54            except IOError, exc:
55                if exc[0] == errno.EINTR:
56                    continue
57                else:
58                    raise
59            if not line:
60                break
61            if self.handlevirtualhost:
62                yield line.split(None, 1)
63            else:
64                yield (None, line)
65
66    def writeline(self, utclogdate, server, line):
67        if not self.utcrotate:
68            utclogdate += self.localutcoffset
69        filename = utclogdate.strftime(self.pattern)
70        if self.handlevirtualhost:
71            filename = filename.replace("%v", server)
72        if server in self.servers:
73            f = self.servers[server]
74            if f.name != filename:
75                f.flush()
76                f.close()
77                self.updateutcoffset()
78                self.servers[server] = f = self.openfile(filename)
79        else:
80            self.servers[server] = f = self.openfile(filename)
81        f.write(line)
82        f.flush()
83
84    def run_unbuffered(self):
85        for (server, data) in self.readlines():
86            datestring = self.re_find_date.findall(data)[0]
87            utclogdate = self.apachedate2utc(datestring)
88            self.writeline(utclogdate, server, data)
89
90    def run_buffered(self):
91        signal.signal(signal.SIGHUP, self.sighandler)
92        signal.signal(signal.SIGTERM, self.sighandler)
93        signal.signal(signal.SIGINT, self.sighandler)
94        signal.signal(signal.SIGQUIT, self.sighandler)
95        try:
96            for (server, data) in self.readlines():
97                try:
98                    datestring = self.re_find_date.findall(data)[0]
99                    utclogdate = self.apachedate2utc(datestring)
100                    self.add(LogLine((utclogdate, server, data)))
101                except Exception, exc:
102                    print server, data
103                    raise
104        except Exception, exc:
105            self.flushall()
106            raise
107
108    def add(self, logline):
109        if not self.data or self.data[-1] <= logline:
110            self.data.append(logline)
111        else:
112            bisect.insort_right(self.data, logline)
113        self.flush()
114
115    def flushall(self):
116        for (utclogdate, server, logdata) in self.data:
117            self.writeline(utclogdate, server, logdata)
118        self.data = []
119
120    def flush(self):
121        while self.data:
122            line = self.data[0]
123            (utclogdate, server, logdata) = line
124            if datetime.datetime.utcnow() - utclogdate < self.buffertime:
125                return
126            self.writeline(utclogdate, server, logdata)
127            self.data.pop(0)
128
129    def apachedate2utc(self, d):
130        temp = d.split()
131        utcdate = datetime.datetime(*(time.strptime(temp[0], "%d/%b/%Y:%H:%M:%S")[0:6])) # support ancient distributions with python < 2.5
132        minsoff = int("%s%s" % (temp[1][0], temp[1][-2:]))
133        hrsoff = int("%s%s" % (temp[1][0], temp[1][1:3]))
134        utcdate -= datetime.timedelta(hours=hrsoff, minutes=minsoff)
135        return utcdate
136
137    def updateutcoffset(self):
138        temp = datetime.datetime.now() - datetime.datetime.utcnow()
139        self.localutcoffset = datetime.timedelta(days=temp.days, seconds=temp.seconds+1, microseconds=0)
140
141    def sighandler(self, signum, frame):
142        self.flushall()
143        if signum in (signal.SIGQUIT, signal.SIGTERM, signal.SIGINT):
144            sys.exit(signum)
145
146
147def main(args=None):
148    import optparse
149    p = optparse.OptionParser(usage="usage: %prog filename-pattern [options]\nIf you use virtual hosts please note that the virtual host column (%v) has to be the first column in every logfile!")
150    p.add_option("-z", "--gzip", dest="gzip", type="int", action="store", help="If set, logs are gzipped with this compression level (lowest: 1, highest: 9)", default=None)
151    p.add_option("-b", "--buffertime", dest="buffertime", type="int", action="store", help="Time in seconds for which log entries are buffered, default=0. Set to 0 to disable buffering", default=0)
152    p.add_option("-u", "--utcrotate", dest="utcrotate", action="store_true", help="If set, UTC time determines the time for filenames and rotation. Otherwise local time is used.", default=False)
153    (options, args) = p.parse_args()
154    if options.gzip is not None:
155        if options.gzip < 1:
156            options.gzip = 1
157        elif options.gzip > 9:
158            options.gzip = 9
159    if len(args) != 1:
160        p.print_usage(sys.stderr)
161        sys.stderr.write("%s: We need a filename-pattern\n" % p.get_prog_name())
162        sys.stderr.flush()
163        return 1
164    buf = Buffer(pattern=args[0], gzip_logs=options.gzip, buffertime=options.buffertime, utcrotate=options.utcrotate)
165    buf.run()
166
167
168if __name__ == "__main__":
169    sys.exit(main())
Note: See TracBrowser for help on using the browser.