root/livinglogic.python.tipimaid/liaalh.py @ 63:dde8ac8eb16d

Revision 63:dde8ac8eb16d, 5.6 KB (checked in by Nikolas Tautenhahn <nik@…>, 11 years ago)

ignore malformed loglines

  • 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            try:
87                datestring = self.re_find_date.findall(data)[0]
88                utclogdate = self.apachedate2utc(datestring)
89                self.writeline(utclogdate, server, data)
90            except IndexError, exc: # index error bc we didn't find an apache date -> malformed logline
91                continue # ignore it
92
93    def run_buffered(self):
94        signal.signal(signal.SIGHUP, self.sighandler)
95        signal.signal(signal.SIGTERM, self.sighandler)
96        signal.signal(signal.SIGINT, self.sighandler)
97        signal.signal(signal.SIGQUIT, self.sighandler)
98        try:
99            for (server, data) in self.readlines():
100                try:
101                    datestring = self.re_find_date.findall(data)[0]
102                    utclogdate = self.apachedate2utc(datestring)
103                    self.add(LogLine((utclogdate, server, data)))
104                except IndexError, exc: # index error bc we didn't find an apache date -> malformed logline
105                    continue # ignore it
106                except Exception, exc:
107                    print server, data
108                    raise
109        except Exception, exc:
110            self.flushall()
111            raise
112
113    def add(self, logline):
114        if not self.data or self.data[-1] <= logline:
115            self.data.append(logline)
116        else:
117            bisect.insort_right(self.data, logline)
118        self.flush()
119
120    def flushall(self):
121        for (utclogdate, server, logdata) in self.data:
122            self.writeline(utclogdate, server, logdata)
123        self.data = []
124
125    def flush(self):
126        while self.data:
127            line = self.data[0]
128            (utclogdate, server, logdata) = line
129            if datetime.datetime.utcnow() - utclogdate < self.buffertime:
130                return
131            self.writeline(utclogdate, server, logdata)
132            self.data.pop(0)
133
134    def apachedate2utc(self, d):
135        temp = d.split()
136        utcdate = datetime.datetime(*(time.strptime(temp[0], "%d/%b/%Y:%H:%M:%S")[0:6])) # support ancient distributions with python < 2.5
137        minsoff = int("%s%s" % (temp[1][0], temp[1][-2:]))
138        hrsoff = int("%s%s" % (temp[1][0], temp[1][1:3]))
139        utcdate -= datetime.timedelta(hours=hrsoff, minutes=minsoff)
140        return utcdate
141
142    def updateutcoffset(self):
143        temp = datetime.datetime.now() - datetime.datetime.utcnow()
144        self.localutcoffset = datetime.timedelta(days=temp.days, seconds=temp.seconds+1, microseconds=0)
145
146    def sighandler(self, signum, frame):
147        self.flushall()
148        if signum in (signal.SIGQUIT, signal.SIGTERM, signal.SIGINT):
149            sys.exit(signum)
150
151
152def main(args=None):
153    import optparse
154    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!")
155    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)
156    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)
157    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)
158    (options, args) = p.parse_args()
159    if options.gzip is not None:
160        if options.gzip < 1:
161            options.gzip = 1
162        elif options.gzip > 9:
163            options.gzip = 9
164    if len(args) != 1:
165        p.print_usage(sys.stderr)
166        sys.stderr.write("%s: We need a filename-pattern\n" % p.get_prog_name())
167        sys.stderr.flush()
168        return 1
169    buf = Buffer(pattern=args[0], gzip_logs=options.gzip, buffertime=options.buffertime, utcrotate=options.utcrotate)
170    buf.run()
171
172
173if __name__ == "__main__":
174    sys.exit(main())
Note: See TracBrowser for help on using the browser.