3 ## echo | compresslog /path/to/my/log
16 ## Setup logging to syslog bright and early
18 # this will route stderr to syslog, including
19 # stacktraces from unhandled exceptions
21 if sys
.argv
[1] == '-d': # "debug" mode disables logging to syslog
25 plogger
= subprocess
.Popen(['/usr/bin/logger', '-t',
26 'compresslog[%u]' % os
.getpid()],
27 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
29 os
.dup2(plogger
.stdin
.fileno(), sys
.stderr
.fileno())
31 ## init globals used in sig handler
32 # fh in append, binary
37 sys
.stderr
.write(msg
+'\n')
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.
61 if os
.path
.exists(fname
):
62 if not renamedwithpid
:
63 # rename, using our pid for uniqueness
64 pidstr
= '_%u' % os
.getpid()
66 if fname
.endswith('_log'):
67 fname
= fname
[0:-4] + pidstr
+ '_log'
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')
76 fcntl
.flock(f
, fcntl
.LOCK_EX | fcntl
.LOCK_NB
)
78 if e
.errno
== errno
.EACCES
or e
.errno
== errno
.EAGAIN
:
79 sys
.stderr
.write("Cannot flock %s\n" % fname
)
81 errorprint("Lost race with self? exiting")
84 errorprint("Unexpected error in flock(): %u" % e
.errno
)
86 # success - out of the error handling loop
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
101 sig_wants_flushexit
= True
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
)
121 rfds
= select
.select([sys
.stdin
], [], [], timeout
)[0]
122 debugprint('select: %u' % len(rfds
))
123 if len(rfds
): # only read() when select says
126 buf
= sys
.stdin
.read(4096)
127 debugprint('read %u' % len(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
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
145 debugprint('E in read() or write(): %u' % e
[0])
146 if e
[0] == errno
.EINTR
:
147 continue # on signal, restart at the top
151 if at_eof
or sig_wants_flushexit
:
154 debugprint('fh closed, exiting')
157 sig_wants_flush
= False
159 f
.flush(zlib
.Z_FULL_FLUSH
)
162 debugprint('E in flush')
163 # ignore exceptions, try to keep rolling