compresslog: fix thinko on debug var
[compresslog.git] / compresslog
blob76982d41e71b1e6e902aa62eb98a552cbe1ed122
1 #!/usr/bin/python
3 ## echo | compresslog /path/to/my/log
5 import os
6 import sys
7 import select
8 import signal
9 import gzip
10 import zlib
11 import subprocess
12 import fcntl
13 import errno
16 ## Setup logging to syslog bright and early
17 ##
18 # this will route stderr to syslog, including
19 # stacktraces from unhandled exceptions
20 debug = False
21 if sys.argv[1] == '-d': # "debug" mode disables logging to syslog
22 debug = True
23 sys.argv.pop(1)
24 else:
25 plogger = subprocess.Popen(['/usr/bin/logger', '-t',
26 'compresslog[%u]' % os.getpid()],
27 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
28 close_fds=True )
29 os.dup2(plogger.stdin.fileno(), sys.stderr.fileno())
31 ## init globals used in sig handler
32 # fh in append, binary
33 fname = sys.argv[1]
35 def debugprint(msg):
36 if debug:
37 sys.stderr.write(msg +'\n')
38 def errorprint(msg):
39 sys.stderr.write(msg +'\n')
41 ## Work around several issues
43 ## - gzip files, due to their nature cannot sustain two simultaneous
44 ## writers. Pure text log files can, thanks to promises from POSIX
45 ## implemented in the kernel about the integrity of writes of
46 ## newline-delimited data.
48 ## - gzip files have headers and checksums at the end. So we cannot
49 ## just append to an existing (old, closed). Fedora 19/RHEL7 libs
50 ## seem to handle concatenated gzip files transparently but our
51 ## deployment platform is RHEL6
53 ## - Apache 2.2 will sometimes open multiple piped loggers to the same
54 ## file. Apache 2.4 seems to only do it if the piped logger config
55 ## string is different. v2.2 is less predictable on this.
56 ## This means several compresslog processes are started at the same
57 ## time and race to create the file.
59 renamedwithpid=False
60 while True:
61 if os.path.exists(fname):
62 if not renamedwithpid:
63 # rename, using our pid for uniqueness
64 pidstr = '_%u' % os.getpid()
65 renamedwithpid=True
66 if fname.endswith('_log'):
67 fname = fname[0:-4] + pidstr + '_log'
68 else:
69 fname = fname + pidstr
70 errorprint("Destination file exists, writing to "
71 + fname + " instead.")
73 ## Try to open file exclusively
74 f = gzip.open(fname, 'wb')
75 try:
76 fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
77 except IOError, e:
78 if e.errno == errno.EACCES or e.errno == errno.EAGAIN:
79 sys.stderr.write("Cannot flock %s\n" % fname)
80 if renamedwithpid:
81 errorprint("Lost race with self? exiting")
82 sys.exit(1)
83 else:
84 errorprint("Unexpected error in flock(): %u" % e.errno)
85 raise
86 # success - out of the error handling loop
87 break
89 sig_wants_flush = False
90 sig_wants_flushexit = False
92 def sig_flush(signum, frame):
93 debugprint('sig_flush')
94 global sig_wants_flush
95 sig_wants_flush = True
97 def sig_flushexit(signum, frame):
98 debugprint('sig_flushexit')
99 global sig_wants_flushexit
100 global timeout
101 sig_wants_flushexit = True
102 timeout = 0
104 ## ensure we flush on various signals
105 # USR1/USR2: user asked to flush out
106 # HUP: apache is no longer writing to us (hung up)
107 signal.signal(signal.SIGUSR1, sig_flush)
108 signal.signal(signal.SIGUSR2, sig_flush)
109 signal.signal(signal.SIGHUP, sig_flushexit)
110 signal.signal(signal.SIGTERM, sig_flushexit)
112 # while True / select / read(int) / O_NONBLOCK put
113 # input buffering explicitly in our hands
114 fcntl.fcntl(sys.stdin, fcntl.F_SETFL, os.O_NONBLOCK)
116 at_eof=False
117 timeout=60
118 while True:
119 buf = False
120 try:
121 rfds = select.select([sys.stdin], [], [], timeout)[0]
122 debugprint('select: %u' % len(rfds))
123 if len(rfds): # only read() when select says
124 debugprint('read()')
125 # will not block
126 buf = sys.stdin.read(4096)
127 debugprint('read %u' % len(buf))
128 if buf:
129 # If we do get EINTR during write, in Python < 2.7.2
130 # the write may be screwed/incomplete, but we
131 # have no way to know&retry.
132 # http://bugs.python.org/issue10956
133 f.write(buf)
134 debugprint('wrote')
135 else:
136 at_eof = True
137 debugprint('at_eof')
138 except select.error, e:
139 if e[0] == errno.EINTR:
140 debugprint('E in select: %u' % e[0])
141 continue # on signal, restart at the top
142 else:
143 raise
144 except IOError, e:
145 debugprint('E in read() or write(): %u' % e[0])
146 if e[0] == errno.EINTR:
147 continue # on signal, restart at the top
148 else:
149 raise
151 if at_eof or sig_wants_flushexit:
152 f.close()
153 sys.stdin.close()
154 debugprint('fh closed, exiting')
155 sys.exit(0)
156 if sig_wants_flush:
157 sig_wants_flush = False
158 try:
159 f.flush(zlib.Z_FULL_FLUSH)
160 debugprint('flush')
161 except:
162 debugprint('E in flush')
163 # ignore exceptions, try to keep rolling
164 pass