blob: 41cbca11db9d08702ffc14608c565248da0ab44d [file] [log] [blame]
Vinay Sajip4600f112005-03-13 09:56:36 +00001# Copyright 2001-2005 by Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +00002#
3# Permission to use, copy, modify, and distribute this software and its
4# documentation for any purpose and without fee is hereby granted,
5# provided that the above copyright notice appear in all copies and that
6# both that copyright notice and this permission notice appear in
7# supporting documentation, and that the name of Vinay Sajip
8# not be used in advertising or publicity pertaining to distribution
9# of the software without specific, written prior permission.
10# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
11# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
12# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
13# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
14# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Guido van Rossum57102f82002-11-13 16:15:58 +000016
17"""
Vinay Sajip3f742842004-02-28 16:07:46 +000018Additional handlers for the logging package for Python. The core package is
19based on PEP 282 and comments thereto in comp.lang.python, and influenced by
20Apache's log4j system.
Guido van Rossum57102f82002-11-13 16:15:58 +000021
22Should work under Python versions >= 1.5.2, except that source line
Vinay Sajip48cfe382004-02-20 13:17:27 +000023information is not available unless 'sys._getframe()' is.
Guido van Rossum57102f82002-11-13 16:15:58 +000024
Vinay Sajip48cfe382004-02-20 13:17:27 +000025Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000026
27To use, simply 'import logging' and log away!
28"""
29
Guido van Rossumba205d62006-08-17 08:57:26 +000030import sys, logging, socket, types, os, string, struct, time, glob
31try:
32 import cPickle as pickle
33except ImportError:
34 import pickle
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +000035from stat import ST_DEV, ST_INO
Guido van Rossum57102f82002-11-13 16:15:58 +000036
Vinay Sajip4600f112005-03-13 09:56:36 +000037try:
38 import codecs
39except ImportError:
40 codecs = None
41
Guido van Rossum57102f82002-11-13 16:15:58 +000042#
43# Some constants...
44#
45
46DEFAULT_TCP_LOGGING_PORT = 9020
47DEFAULT_UDP_LOGGING_PORT = 9021
48DEFAULT_HTTP_LOGGING_PORT = 9022
49DEFAULT_SOAP_LOGGING_PORT = 9023
50SYSLOG_UDP_PORT = 514
51
Thomas Wouters477c8d52006-05-27 19:21:47 +000052_MIDNIGHT = 24 * 60 * 60 # number of seconds in a day
53
Vinay Sajip17c52d82004-07-03 11:48:34 +000054class BaseRotatingHandler(logging.FileHandler):
55 """
56 Base class for handlers that rotate log files at a certain point.
57 Not meant to be instantiated directly. Instead, use RotatingFileHandler
58 or TimedRotatingFileHandler.
59 """
Vinay Sajip4600f112005-03-13 09:56:36 +000060 def __init__(self, filename, mode, encoding=None):
Vinay Sajip17c52d82004-07-03 11:48:34 +000061 """
62 Use the specified filename for streamed logging
63 """
Vinay Sajip4600f112005-03-13 09:56:36 +000064 if codecs is None:
65 encoding = None
66 logging.FileHandler.__init__(self, filename, mode, encoding)
67 self.mode = mode
68 self.encoding = encoding
Guido van Rossum57102f82002-11-13 16:15:58 +000069
Vinay Sajip17c52d82004-07-03 11:48:34 +000070 def emit(self, record):
71 """
72 Emit a record.
73
74 Output the record to the file, catering for rollover as described
75 in doRollover().
76 """
Vinay Sajip3970c112004-07-08 10:24:04 +000077 try:
78 if self.shouldRollover(record):
79 self.doRollover()
80 logging.FileHandler.emit(self, record)
Vinay Sajip85c19092005-10-31 13:14:19 +000081 except (KeyboardInterrupt, SystemExit):
82 raise
Vinay Sajip3970c112004-07-08 10:24:04 +000083 except:
84 self.handleError(record)
Vinay Sajip17c52d82004-07-03 11:48:34 +000085
86class RotatingFileHandler(BaseRotatingHandler):
87 """
88 Handler for logging to a set of files, which switches from one file
89 to the next when the current file reaches a certain size.
90 """
Vinay Sajip4600f112005-03-13 09:56:36 +000091 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None):
Guido van Rossum57102f82002-11-13 16:15:58 +000092 """
93 Open the specified file and use it as the stream for logging.
94
95 By default, the file grows indefinitely. You can specify particular
96 values of maxBytes and backupCount to allow the file to rollover at
97 a predetermined size.
98
99 Rollover occurs whenever the current log file is nearly maxBytes in
100 length. If backupCount is >= 1, the system will successively create
101 new files with the same pathname as the base file, but with extensions
102 ".1", ".2" etc. appended to it. For example, with a backupCount of 5
103 and a base file name of "app.log", you would get "app.log",
104 "app.log.1", "app.log.2", ... through to "app.log.5". The file being
105 written to is always "app.log" - when it gets filled up, it is closed
106 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
107 exist, then they are renamed to "app.log.2", "app.log.3" etc.
108 respectively.
109
110 If maxBytes is zero, rollover never occurs.
111 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000112 if maxBytes > 0:
Vinay Sajip4600f112005-03-13 09:56:36 +0000113 mode = 'a' # doesn't make sense otherwise!
114 BaseRotatingHandler.__init__(self, filename, mode, encoding)
Guido van Rossum57102f82002-11-13 16:15:58 +0000115 self.maxBytes = maxBytes
116 self.backupCount = backupCount
Guido van Rossum57102f82002-11-13 16:15:58 +0000117
118 def doRollover(self):
119 """
120 Do a rollover, as described in __init__().
121 """
122
123 self.stream.close()
124 if self.backupCount > 0:
125 for i in range(self.backupCount - 1, 0, -1):
126 sfn = "%s.%d" % (self.baseFilename, i)
127 dfn = "%s.%d" % (self.baseFilename, i + 1)
128 if os.path.exists(sfn):
129 #print "%s -> %s" % (sfn, dfn)
130 if os.path.exists(dfn):
131 os.remove(dfn)
132 os.rename(sfn, dfn)
133 dfn = self.baseFilename + ".1"
134 if os.path.exists(dfn):
135 os.remove(dfn)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000136 os.rename(self.baseFilename, dfn)
Guido van Rossum57102f82002-11-13 16:15:58 +0000137 #print "%s -> %s" % (self.baseFilename, dfn)
Vinay Sajip4600f112005-03-13 09:56:36 +0000138 if self.encoding:
139 self.stream = codecs.open(self.baseFilename, 'w', self.encoding)
140 else:
141 self.stream = open(self.baseFilename, 'w')
Guido van Rossum57102f82002-11-13 16:15:58 +0000142
Vinay Sajip17c52d82004-07-03 11:48:34 +0000143 def shouldRollover(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000144 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000145 Determine if rollover should occur.
Guido van Rossum57102f82002-11-13 16:15:58 +0000146
Vinay Sajip17c52d82004-07-03 11:48:34 +0000147 Basically, see if the supplied record would cause the file to exceed
148 the size limit we have.
Guido van Rossum57102f82002-11-13 16:15:58 +0000149 """
150 if self.maxBytes > 0: # are we rolling over?
151 msg = "%s\n" % self.format(record)
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000152 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
Guido van Rossum57102f82002-11-13 16:15:58 +0000153 if self.stream.tell() + len(msg) >= self.maxBytes:
Vinay Sajip17c52d82004-07-03 11:48:34 +0000154 return 1
155 return 0
Guido van Rossum57102f82002-11-13 16:15:58 +0000156
Vinay Sajip17c52d82004-07-03 11:48:34 +0000157class TimedRotatingFileHandler(BaseRotatingHandler):
158 """
159 Handler for logging to a file, rotating the log file at certain timed
160 intervals.
161
162 If backupCount is > 0, when rollover is done, no more than backupCount
163 files are kept - the oldest ones are deleted.
164 """
Vinay Sajip4600f112005-03-13 09:56:36 +0000165 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None):
166 BaseRotatingHandler.__init__(self, filename, 'a', encoding)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000167 self.when = string.upper(when)
168 self.backupCount = backupCount
169 # Calculate the real rollover interval, which is just the number of
170 # seconds between rollovers. Also set the filename suffix used when
171 # a rollover occurs. Current 'when' events supported:
172 # S - Seconds
173 # M - Minutes
174 # H - Hours
175 # D - Days
176 # midnight - roll over at midnight
177 # W{0-6} - roll over on a certain day; 0 - Monday
178 #
179 # Case of the 'when' specifier is not important; lower or upper case
180 # will work.
181 currentTime = int(time.time())
182 if self.when == 'S':
183 self.interval = 1 # one second
184 self.suffix = "%Y-%m-%d_%H-%M-%S"
185 elif self.when == 'M':
186 self.interval = 60 # one minute
187 self.suffix = "%Y-%m-%d_%H-%M"
188 elif self.when == 'H':
189 self.interval = 60 * 60 # one hour
190 self.suffix = "%Y-%m-%d_%H"
191 elif self.when == 'D' or self.when == 'MIDNIGHT':
192 self.interval = 60 * 60 * 24 # one day
193 self.suffix = "%Y-%m-%d"
194 elif self.when.startswith('W'):
195 self.interval = 60 * 60 * 24 * 7 # one week
196 if len(self.when) != 2:
197 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
198 if self.when[1] < '0' or self.when[1] > '6':
199 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
200 self.dayOfWeek = int(self.when[1])
201 self.suffix = "%Y-%m-%d"
202 else:
203 raise ValueError("Invalid rollover interval specified: %s" % self.when)
204
Vinay Sajipe7d40662004-10-03 19:12:07 +0000205 self.interval = self.interval * interval # multiply by units requested
Vinay Sajip17c52d82004-07-03 11:48:34 +0000206 self.rolloverAt = currentTime + self.interval
207
208 # If we are rolling over at midnight or weekly, then the interval is already known.
209 # What we need to figure out is WHEN the next interval is. In other words,
210 # if you are rolling over at midnight, then your base interval is 1 day,
211 # but you want to start that one day clock at midnight, not now. So, we
212 # have to fudge the rolloverAt value in order to trigger the first rollover
213 # at the right time. After that, the regular interval will take care of
214 # the rest. Note that this code doesn't care about leap seconds. :)
215 if self.when == 'MIDNIGHT' or self.when.startswith('W'):
216 # This could be done with less code, but I wanted it to be clear
217 t = time.localtime(currentTime)
218 currentHour = t[3]
219 currentMinute = t[4]
220 currentSecond = t[5]
221 # r is the number of seconds left between now and midnight
Thomas Wouters477c8d52006-05-27 19:21:47 +0000222 r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
223 currentSecond)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000224 self.rolloverAt = currentTime + r
225 # If we are rolling over on a certain day, add in the number of days until
226 # the next rollover, but offset by 1 since we just calculated the time
227 # until the next day starts. There are three cases:
228 # Case 1) The day to rollover is today; in this case, do nothing
229 # Case 2) The day to rollover is further in the interval (i.e., today is
230 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
231 # next rollover is simply 6 - 2 - 1, or 3.
232 # Case 3) The day to rollover is behind us in the interval (i.e., today
233 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
234 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
235 # number of days left in the current week (1) plus the number
236 # of days in the next week until the rollover day (3).
237 if when.startswith('W'):
238 day = t[6] # 0 is Monday
239 if day > self.dayOfWeek:
240 daysToWait = (day - self.dayOfWeek) - 1
Vinay Sajipe7d40662004-10-03 19:12:07 +0000241 self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
Vinay Sajip17c52d82004-07-03 11:48:34 +0000242 if day < self.dayOfWeek:
243 daysToWait = (6 - self.dayOfWeek) + day
Vinay Sajipe7d40662004-10-03 19:12:07 +0000244 self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
Vinay Sajip17c52d82004-07-03 11:48:34 +0000245
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000246 #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000247
248 def shouldRollover(self, record):
249 """
250 Determine if rollover should occur
251
252 record is not used, as we are just comparing times, but it is needed so
253 the method siguratures are the same
254 """
255 t = int(time.time())
256 if t >= self.rolloverAt:
257 return 1
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000258 #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000259 return 0
260
261 def doRollover(self):
262 """
263 do a rollover; in this case, a date/time stamp is appended to the filename
264 when the rollover happens. However, you want the file to be named for the
265 start of the interval, not the current time. If there is a backup count,
266 then we have to get a list of matching filenames, sort them and remove
267 the one with the oldest suffix.
268 """
269 self.stream.close()
270 # get the time that this sequence started at and make it a TimeTuple
271 t = self.rolloverAt - self.interval
272 timeTuple = time.localtime(t)
273 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
274 if os.path.exists(dfn):
275 os.remove(dfn)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000276 os.rename(self.baseFilename, dfn)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000277 if self.backupCount > 0:
278 # find the oldest log file and delete it
279 s = glob.glob(self.baseFilename + ".20*")
280 if len(s) > self.backupCount:
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000281 s.sort()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000282 os.remove(s[0])
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000283 #print "%s -> %s" % (self.baseFilename, dfn)
Vinay Sajip4600f112005-03-13 09:56:36 +0000284 if self.encoding:
285 self.stream = codecs.open(self.baseFilename, 'w', self.encoding)
286 else:
287 self.stream = open(self.baseFilename, 'w')
Vinay Sajipd9520412006-01-16 09:13:58 +0000288 self.rolloverAt = self.rolloverAt + self.interval
Guido van Rossum57102f82002-11-13 16:15:58 +0000289
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000290class WatchedFileHandler(logging.FileHandler):
291 """
292 A handler for logging to a file, which watches the file
293 to see if it has changed while in use. This can happen because of
294 usage of programs such as newsyslog and logrotate which perform
295 log file rotation. This handler, intended for use under Unix,
296 watches the file to see if it has changed since the last emit.
297 (A file has changed if its device or inode have changed.)
298 If it has changed, the old file stream is closed, and the file
299 opened to get a new stream.
300
301 This handler is not appropriate for use under Windows, because
302 under Windows open files cannot be moved or renamed - logging
303 opens the files with exclusive locks - and so there is no need
304 for such a handler. Furthermore, ST_INO is not supported under
305 Windows; stat always returns zero for this value.
306
307 This handler is based on a suggestion and patch by Chad J.
308 Schroeder.
309 """
310 def __init__(self, filename, mode='a', encoding=None):
311 logging.FileHandler.__init__(self, filename, mode, encoding)
312 stat = os.stat(self.baseFilename)
313 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
314
315 def emit(self, record):
316 """
317 Emit a record.
318
319 First check if the underlying file has changed, and if it
320 has, close the old stream and reopen the file to get the
321 current stream.
322 """
323 if not os.path.exists(self.baseFilename):
324 stat = None
325 changed = 1
326 else:
327 stat = os.stat(self.baseFilename)
328 changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
329 if changed:
330 self.stream.flush()
331 self.stream.close()
332 self.stream = self._open()
333 if stat is None:
334 stat = os.stat(self.baseFilename)
335 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
336 logging.FileHandler.emit(self, record)
337
Guido van Rossum57102f82002-11-13 16:15:58 +0000338class SocketHandler(logging.Handler):
339 """
340 A handler class which writes logging records, in pickle format, to
341 a streaming socket. The socket is kept open across logging calls.
342 If the peer resets it, an attempt is made to reconnect on the next call.
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000343 The pickle which is sent is that of the LogRecord's attribute dictionary
344 (__dict__), so that the receiver does not need to have the logging module
345 installed in order to process the logging event.
346
347 To unpickle the record at the receiving end into a LogRecord, use the
348 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000349 """
350
351 def __init__(self, host, port):
352 """
353 Initializes the handler with a specific host address and port.
354
355 The attribute 'closeOnError' is set to 1 - which means that if
356 a socket error occurs, the socket is silently closed and then
357 reopened on the next logging call.
358 """
359 logging.Handler.__init__(self)
360 self.host = host
361 self.port = port
362 self.sock = None
363 self.closeOnError = 0
Vinay Sajip48cfe382004-02-20 13:17:27 +0000364 self.retryTime = None
365 #
366 # Exponential backoff parameters.
367 #
368 self.retryStart = 1.0
369 self.retryMax = 30.0
370 self.retryFactor = 2.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000371
372 def makeSocket(self):
373 """
374 A factory method which allows subclasses to define the precise
375 type of socket they want.
376 """
377 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
378 s.connect((self.host, self.port))
379 return s
380
Vinay Sajip48cfe382004-02-20 13:17:27 +0000381 def createSocket(self):
382 """
383 Try to create a socket, using an exponential backoff with
384 a max retry time. Thanks to Robert Olson for the original patch
385 (SF #815911) which has been slightly refactored.
386 """
387 now = time.time()
388 # Either retryTime is None, in which case this
389 # is the first time back after a disconnect, or
390 # we've waited long enough.
391 if self.retryTime is None:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000392 attempt = 1
Vinay Sajip48cfe382004-02-20 13:17:27 +0000393 else:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000394 attempt = (now >= self.retryTime)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000395 if attempt:
396 try:
397 self.sock = self.makeSocket()
398 self.retryTime = None # next time, no delay before trying
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000399 except socket.error:
Vinay Sajip48cfe382004-02-20 13:17:27 +0000400 #Creation failed, so set the retry time and return.
401 if self.retryTime is None:
402 self.retryPeriod = self.retryStart
403 else:
404 self.retryPeriod = self.retryPeriod * self.retryFactor
405 if self.retryPeriod > self.retryMax:
406 self.retryPeriod = self.retryMax
407 self.retryTime = now + self.retryPeriod
408
Guido van Rossum57102f82002-11-13 16:15:58 +0000409 def send(self, s):
410 """
411 Send a pickled string to the socket.
412
413 This function allows for partial sends which can happen when the
414 network is busy.
415 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000416 if self.sock is None:
417 self.createSocket()
418 #self.sock can be None either because we haven't reached the retry
419 #time yet, or because we have reached the retry time and retried,
420 #but are still unable to connect.
421 if self.sock:
422 try:
423 if hasattr(self.sock, "sendall"):
424 self.sock.sendall(s)
425 else:
426 sentsofar = 0
427 left = len(s)
428 while left > 0:
429 sent = self.sock.send(s[sentsofar:])
430 sentsofar = sentsofar + sent
431 left = left - sent
432 except socket.error:
433 self.sock.close()
434 self.sock = None # so we can call createSocket next time
Guido van Rossum57102f82002-11-13 16:15:58 +0000435
436 def makePickle(self, record):
437 """
438 Pickles the record in binary format with a length prefix, and
439 returns it ready for transmission across the socket.
440 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000441 ei = record.exc_info
442 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000443 dummy = self.format(record) # just to get traceback text into record.exc_text
444 record.exc_info = None # to avoid Unpickleable error
Guido van Rossumba205d62006-08-17 08:57:26 +0000445 s = pickle.dumps(record.__dict__, 1)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000446 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000447 record.exc_info = ei # for next handler
Guido van Rossum57102f82002-11-13 16:15:58 +0000448 slen = struct.pack(">L", len(s))
449 return slen + s
450
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000451 def handleError(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000452 """
453 Handle an error during logging.
454
455 An error has occurred during logging. Most likely cause -
456 connection lost. Close the socket so that we can retry on the
457 next event.
458 """
459 if self.closeOnError and self.sock:
460 self.sock.close()
461 self.sock = None #try to reconnect next time
462 else:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000463 logging.Handler.handleError(self, record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000464
465 def emit(self, record):
466 """
467 Emit a record.
468
469 Pickles the record and writes it to the socket in binary format.
470 If there is an error with the socket, silently drop the packet.
471 If there was a problem with the socket, re-establishes the
472 socket.
473 """
474 try:
475 s = self.makePickle(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000476 self.send(s)
Vinay Sajip85c19092005-10-31 13:14:19 +0000477 except (KeyboardInterrupt, SystemExit):
478 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000479 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000480 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000481
482 def close(self):
483 """
484 Closes the socket.
485 """
486 if self.sock:
487 self.sock.close()
488 self.sock = None
Vinay Sajip48cfe382004-02-20 13:17:27 +0000489 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000490
491class DatagramHandler(SocketHandler):
492 """
493 A handler class which writes logging records, in pickle format, to
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000494 a datagram socket. The pickle which is sent is that of the LogRecord's
495 attribute dictionary (__dict__), so that the receiver does not need to
496 have the logging module installed in order to process the logging event.
497
498 To unpickle the record at the receiving end into a LogRecord, use the
499 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000500
501 """
502 def __init__(self, host, port):
503 """
504 Initializes the handler with a specific host address and port.
505 """
506 SocketHandler.__init__(self, host, port)
507 self.closeOnError = 0
508
509 def makeSocket(self):
510 """
511 The factory method of SocketHandler is here overridden to create
512 a UDP socket (SOCK_DGRAM).
513 """
514 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
515 return s
516
517 def send(self, s):
518 """
519 Send a pickled string to a socket.
520
521 This function no longer allows for partial sends which can happen
522 when the network is busy - UDP does not guarantee delivery and
523 can deliver packets out of sequence.
524 """
Vinay Sajipfb154172004-08-24 09:36:23 +0000525 if self.sock is None:
526 self.createSocket()
Guido van Rossum57102f82002-11-13 16:15:58 +0000527 self.sock.sendto(s, (self.host, self.port))
528
529class SysLogHandler(logging.Handler):
530 """
531 A handler class which sends formatted logging records to a syslog
532 server. Based on Sam Rushing's syslog module:
533 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
534 Contributed by Nicolas Untz (after which minor refactoring changes
535 have been made).
536 """
537
538 # from <linux/sys/syslog.h>:
539 # ======================================================================
540 # priorities/facilities are encoded into a single 32-bit quantity, where
541 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
542 # facility (0-big number). Both the priorities and the facilities map
543 # roughly one-to-one to strings in the syslogd(8) source code. This
544 # mapping is included in this file.
545 #
546 # priorities (these are ordered)
547
548 LOG_EMERG = 0 # system is unusable
549 LOG_ALERT = 1 # action must be taken immediately
550 LOG_CRIT = 2 # critical conditions
551 LOG_ERR = 3 # error conditions
552 LOG_WARNING = 4 # warning conditions
553 LOG_NOTICE = 5 # normal but significant condition
554 LOG_INFO = 6 # informational
555 LOG_DEBUG = 7 # debug-level messages
556
557 # facility codes
558 LOG_KERN = 0 # kernel messages
559 LOG_USER = 1 # random user-level messages
560 LOG_MAIL = 2 # mail system
561 LOG_DAEMON = 3 # system daemons
562 LOG_AUTH = 4 # security/authorization messages
563 LOG_SYSLOG = 5 # messages generated internally by syslogd
564 LOG_LPR = 6 # line printer subsystem
565 LOG_NEWS = 7 # network news subsystem
566 LOG_UUCP = 8 # UUCP subsystem
567 LOG_CRON = 9 # clock daemon
568 LOG_AUTHPRIV = 10 # security/authorization messages (private)
569
570 # other codes through 15 reserved for system use
571 LOG_LOCAL0 = 16 # reserved for local use
572 LOG_LOCAL1 = 17 # reserved for local use
573 LOG_LOCAL2 = 18 # reserved for local use
574 LOG_LOCAL3 = 19 # reserved for local use
575 LOG_LOCAL4 = 20 # reserved for local use
576 LOG_LOCAL5 = 21 # reserved for local use
577 LOG_LOCAL6 = 22 # reserved for local use
578 LOG_LOCAL7 = 23 # reserved for local use
579
580 priority_names = {
581 "alert": LOG_ALERT,
582 "crit": LOG_CRIT,
583 "critical": LOG_CRIT,
584 "debug": LOG_DEBUG,
585 "emerg": LOG_EMERG,
586 "err": LOG_ERR,
587 "error": LOG_ERR, # DEPRECATED
588 "info": LOG_INFO,
589 "notice": LOG_NOTICE,
590 "panic": LOG_EMERG, # DEPRECATED
591 "warn": LOG_WARNING, # DEPRECATED
592 "warning": LOG_WARNING,
593 }
594
595 facility_names = {
596 "auth": LOG_AUTH,
597 "authpriv": LOG_AUTHPRIV,
598 "cron": LOG_CRON,
599 "daemon": LOG_DAEMON,
600 "kern": LOG_KERN,
601 "lpr": LOG_LPR,
602 "mail": LOG_MAIL,
603 "news": LOG_NEWS,
604 "security": LOG_AUTH, # DEPRECATED
605 "syslog": LOG_SYSLOG,
606 "user": LOG_USER,
607 "uucp": LOG_UUCP,
608 "local0": LOG_LOCAL0,
609 "local1": LOG_LOCAL1,
610 "local2": LOG_LOCAL2,
611 "local3": LOG_LOCAL3,
612 "local4": LOG_LOCAL4,
613 "local5": LOG_LOCAL5,
614 "local6": LOG_LOCAL6,
615 "local7": LOG_LOCAL7,
616 }
617
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000618 #The map below appears to be trivially lowercasing the key. However,
619 #there's more to it than meets the eye - in some locales, lowercasing
620 #gives unexpected results. See SF #1524081: in the Turkish locale,
621 #"INFO".lower() != "info"
622 priority_map = {
623 "DEBUG" : "debug",
624 "INFO" : "info",
625 "WARNING" : "warning",
626 "ERROR" : "error",
627 "CRITICAL" : "critical"
628 }
629
Guido van Rossum57102f82002-11-13 16:15:58 +0000630 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER):
631 """
632 Initialize a handler.
633
634 If address is specified as a string, UNIX socket is used.
635 If facility is not specified, LOG_USER is used.
636 """
637 logging.Handler.__init__(self)
638
639 self.address = address
640 self.facility = facility
641 if type(address) == types.StringType:
Guido van Rossum57102f82002-11-13 16:15:58 +0000642 self.unixsocket = 1
Thomas Wouters89f507f2006-12-13 04:49:30 +0000643 self._connect_unixsocket(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000644 else:
Guido van Rossum57102f82002-11-13 16:15:58 +0000645 self.unixsocket = 0
Thomas Wouters89f507f2006-12-13 04:49:30 +0000646 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Guido van Rossum57102f82002-11-13 16:15:58 +0000647
648 self.formatter = None
649
Vinay Sajipa1974c12005-01-13 08:23:56 +0000650 def _connect_unixsocket(self, address):
651 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
652 # syslog may require either DGRAM or STREAM sockets
653 try:
654 self.socket.connect(address)
655 except socket.error:
656 self.socket.close()
657 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Vinay Sajip8b6b53f2005-11-09 13:55:13 +0000658 self.socket.connect(address)
Vinay Sajipa1974c12005-01-13 08:23:56 +0000659
Guido van Rossum57102f82002-11-13 16:15:58 +0000660 # curious: when talking to the unix-domain '/dev/log' socket, a
661 # zero-terminator seems to be required. this string is placed
662 # into a class variable so that it can be overridden if
663 # necessary.
664 log_format_string = '<%d>%s\000'
665
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000666 def encodePriority(self, facility, priority):
Guido van Rossum57102f82002-11-13 16:15:58 +0000667 """
668 Encode the facility and priority. You can pass in strings or
669 integers - if strings are passed, the facility_names and
670 priority_names mapping dictionaries are used to convert them to
671 integers.
672 """
673 if type(facility) == types.StringType:
674 facility = self.facility_names[facility]
675 if type(priority) == types.StringType:
676 priority = self.priority_names[priority]
677 return (facility << 3) | priority
678
679 def close (self):
680 """
681 Closes the socket.
682 """
683 if self.unixsocket:
684 self.socket.close()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000685 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000686
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000687 def mapPriority(self, levelName):
688 """
689 Map a logging level name to a key in the priority_names map.
690 This is useful in two scenarios: when custom levels are being
691 used, and in the case where you can't do a straightforward
692 mapping by lowercasing the logging level name because of locale-
693 specific issues (see SF #1524081).
694 """
695 return self.priority_map.get(levelName, "warning")
696
Guido van Rossum57102f82002-11-13 16:15:58 +0000697 def emit(self, record):
698 """
699 Emit a record.
700
701 The record is formatted, and then sent to the syslog server. If
702 exception information is present, it is NOT sent to the server.
703 """
704 msg = self.format(record)
705 """
706 We need to convert record level to lowercase, maybe this will
707 change in the future.
708 """
709 msg = self.log_format_string % (
710 self.encodePriority(self.facility,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000711 self.mapPriority(record.levelname)),
712 msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000713 try:
714 if self.unixsocket:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000715 try:
716 self.socket.send(msg)
717 except socket.error:
718 self._connect_unixsocket(self.address)
719 self.socket.send(msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000720 else:
721 self.socket.sendto(msg, self.address)
Vinay Sajip85c19092005-10-31 13:14:19 +0000722 except (KeyboardInterrupt, SystemExit):
723 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000724 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000725 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000726
727class SMTPHandler(logging.Handler):
728 """
729 A handler class which sends an SMTP email for each logging event.
730 """
731 def __init__(self, mailhost, fromaddr, toaddrs, subject):
732 """
733 Initialize the handler.
734
735 Initialize the instance with the from and to addresses and subject
736 line of the email. To specify a non-standard SMTP port, use the
737 (host, port) tuple format for the mailhost argument.
738 """
739 logging.Handler.__init__(self)
740 if type(mailhost) == types.TupleType:
741 host, port = mailhost
742 self.mailhost = host
743 self.mailport = port
744 else:
745 self.mailhost = mailhost
746 self.mailport = None
747 self.fromaddr = fromaddr
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000748 if type(toaddrs) == types.StringType:
749 toaddrs = [toaddrs]
Guido van Rossum57102f82002-11-13 16:15:58 +0000750 self.toaddrs = toaddrs
751 self.subject = subject
752
753 def getSubject(self, record):
754 """
755 Determine the subject for the email.
756
757 If you want to specify a subject line which is record-dependent,
758 override this method.
759 """
760 return self.subject
761
Vinay Sajipe7d40662004-10-03 19:12:07 +0000762 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
763
764 monthname = [None,
765 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
766 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
767
768 def date_time(self):
769 """
770 Return the current date and time formatted for a MIME header.
771 Needed for Python 1.5.2 (no email package available)
772 """
773 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
774 s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
775 self.weekdayname[wd],
776 day, self.monthname[month], year,
777 hh, mm, ss)
778 return s
779
Guido van Rossum57102f82002-11-13 16:15:58 +0000780 def emit(self, record):
781 """
782 Emit a record.
783
784 Format the record and send it to the specified addressees.
785 """
786 try:
787 import smtplib
Vinay Sajipe7d40662004-10-03 19:12:07 +0000788 try:
789 from email.Utils import formatdate
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000790 except ImportError:
Vinay Sajipe7d40662004-10-03 19:12:07 +0000791 formatdate = self.date_time
Guido van Rossum57102f82002-11-13 16:15:58 +0000792 port = self.mailport
793 if not port:
794 port = smtplib.SMTP_PORT
795 smtp = smtplib.SMTP(self.mailhost, port)
796 msg = self.format(record)
Neal Norwitzf297bd12003-04-23 03:49:43 +0000797 msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (
Guido van Rossum57102f82002-11-13 16:15:58 +0000798 self.fromaddr,
799 string.join(self.toaddrs, ","),
Neal Norwitzf297bd12003-04-23 03:49:43 +0000800 self.getSubject(record),
Martin v. Löwis318a12e2004-08-18 12:27:40 +0000801 formatdate(), msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000802 smtp.sendmail(self.fromaddr, self.toaddrs, msg)
803 smtp.quit()
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000804 except (KeyboardInterrupt, SystemExit):
805 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000806 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000807 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000808
809class NTEventLogHandler(logging.Handler):
810 """
811 A handler class which sends events to the NT Event Log. Adds a
812 registry entry for the specified application name. If no dllname is
813 provided, win32service.pyd (which contains some basic message
814 placeholders) is used. Note that use of these placeholders will make
815 your event logs big, as the entire message source is held in the log.
816 If you want slimmer logs, you have to pass in the name of your own DLL
817 which contains the message definitions you want to use in the event log.
818 """
819 def __init__(self, appname, dllname=None, logtype="Application"):
820 logging.Handler.__init__(self)
821 try:
822 import win32evtlogutil, win32evtlog
823 self.appname = appname
824 self._welu = win32evtlogutil
825 if not dllname:
826 dllname = os.path.split(self._welu.__file__)
827 dllname = os.path.split(dllname[0])
828 dllname = os.path.join(dllname[0], r'win32service.pyd')
829 self.dllname = dllname
830 self.logtype = logtype
831 self._welu.AddSourceToRegistry(appname, dllname, logtype)
832 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
833 self.typemap = {
834 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
835 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000836 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
Guido van Rossum57102f82002-11-13 16:15:58 +0000837 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
838 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
839 }
840 except ImportError:
841 print "The Python Win32 extensions for NT (service, event "\
842 "logging) appear not to be available."
843 self._welu = None
844
845 def getMessageID(self, record):
846 """
847 Return the message ID for the event record. If you are using your
848 own messages, you could do this by having the msg passed to the
849 logger being an ID rather than a formatting string. Then, in here,
850 you could use a dictionary lookup to get the message ID. This
851 version returns 1, which is the base message ID in win32service.pyd.
852 """
853 return 1
854
855 def getEventCategory(self, record):
856 """
857 Return the event category for the record.
858
859 Override this if you want to specify your own categories. This version
860 returns 0.
861 """
862 return 0
863
864 def getEventType(self, record):
865 """
866 Return the event type for the record.
867
868 Override this if you want to specify your own types. This version does
869 a mapping using the handler's typemap attribute, which is set up in
870 __init__() to a dictionary which contains mappings for DEBUG, INFO,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000871 WARNING, ERROR and CRITICAL. If you are using your own levels you will
Guido van Rossum57102f82002-11-13 16:15:58 +0000872 either need to override this method or place a suitable dictionary in
873 the handler's typemap attribute.
874 """
875 return self.typemap.get(record.levelno, self.deftype)
876
877 def emit(self, record):
878 """
879 Emit a record.
880
881 Determine the message ID, event category and event type. Then
882 log the message in the NT event log.
883 """
884 if self._welu:
885 try:
886 id = self.getMessageID(record)
887 cat = self.getEventCategory(record)
888 type = self.getEventType(record)
889 msg = self.format(record)
890 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000891 except (KeyboardInterrupt, SystemExit):
892 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000893 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000894 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000895
896 def close(self):
897 """
898 Clean up this handler.
899
900 You can remove the application name from the registry as a
901 source of event log entries. However, if you do this, you will
902 not be able to see the events as you intended in the Event Log
903 Viewer - it needs to be able to access the registry to get the
904 DLL name.
905 """
906 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000907 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000908
909class HTTPHandler(logging.Handler):
910 """
911 A class which sends records to a Web server, using either GET or
912 POST semantics.
913 """
914 def __init__(self, host, url, method="GET"):
915 """
916 Initialize the instance with the host, the request URL, and the method
917 ("GET" or "POST")
918 """
919 logging.Handler.__init__(self)
920 method = string.upper(method)
921 if method not in ["GET", "POST"]:
922 raise ValueError, "method must be GET or POST"
923 self.host = host
924 self.url = url
925 self.method = method
926
Neal Norwitzf297bd12003-04-23 03:49:43 +0000927 def mapLogRecord(self, record):
928 """
929 Default implementation of mapping the log record into a dict
Vinay Sajip48cfe382004-02-20 13:17:27 +0000930 that is sent as the CGI data. Overwrite in your class.
Neal Norwitzf297bd12003-04-23 03:49:43 +0000931 Contributed by Franz Glasner.
932 """
933 return record.__dict__
934
Guido van Rossum57102f82002-11-13 16:15:58 +0000935 def emit(self, record):
936 """
937 Emit a record.
938
939 Send the record to the Web server as an URL-encoded dictionary
940 """
941 try:
942 import httplib, urllib
Vinay Sajipb7935062005-10-11 13:15:31 +0000943 host = self.host
944 h = httplib.HTTP(host)
Guido van Rossum57102f82002-11-13 16:15:58 +0000945 url = self.url
Neal Norwitzf297bd12003-04-23 03:49:43 +0000946 data = urllib.urlencode(self.mapLogRecord(record))
Guido van Rossum57102f82002-11-13 16:15:58 +0000947 if self.method == "GET":
948 if (string.find(url, '?') >= 0):
949 sep = '&'
950 else:
951 sep = '?'
952 url = url + "%c%s" % (sep, data)
953 h.putrequest(self.method, url)
Vinay Sajipb7935062005-10-11 13:15:31 +0000954 # support multiple hosts on one IP address...
955 # need to strip optional :port from host, if present
956 i = string.find(host, ":")
957 if i >= 0:
958 host = host[:i]
959 h.putheader("Host", host)
Guido van Rossum57102f82002-11-13 16:15:58 +0000960 if self.method == "POST":
Vinay Sajipb7935062005-10-11 13:15:31 +0000961 h.putheader("Content-type",
962 "application/x-www-form-urlencoded")
Guido van Rossum57102f82002-11-13 16:15:58 +0000963 h.putheader("Content-length", str(len(data)))
964 h.endheaders()
965 if self.method == "POST":
966 h.send(data)
967 h.getreply() #can't do anything with the result
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000968 except (KeyboardInterrupt, SystemExit):
969 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000970 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000971 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000972
973class BufferingHandler(logging.Handler):
974 """
975 A handler class which buffers logging records in memory. Whenever each
976 record is added to the buffer, a check is made to see if the buffer should
977 be flushed. If it should, then flush() is expected to do what's needed.
978 """
979 def __init__(self, capacity):
980 """
981 Initialize the handler with the buffer size.
982 """
983 logging.Handler.__init__(self)
984 self.capacity = capacity
985 self.buffer = []
986
987 def shouldFlush(self, record):
988 """
989 Should the handler flush its buffer?
990
991 Returns true if the buffer is up to capacity. This method can be
992 overridden to implement custom flushing strategies.
993 """
994 return (len(self.buffer) >= self.capacity)
995
996 def emit(self, record):
997 """
998 Emit a record.
999
1000 Append the record. If shouldFlush() tells us to, call flush() to process
1001 the buffer.
1002 """
1003 self.buffer.append(record)
1004 if self.shouldFlush(record):
1005 self.flush()
1006
1007 def flush(self):
1008 """
1009 Override to implement custom flushing behaviour.
1010
1011 This version just zaps the buffer to empty.
1012 """
1013 self.buffer = []
1014
Vinay Sajipf42d95e2004-02-21 22:14:34 +00001015 def close(self):
1016 """
1017 Close the handler.
1018
1019 This version just flushes and chains to the parent class' close().
1020 """
1021 self.flush()
1022 logging.Handler.close(self)
1023
Guido van Rossum57102f82002-11-13 16:15:58 +00001024class MemoryHandler(BufferingHandler):
1025 """
1026 A handler class which buffers logging records in memory, periodically
1027 flushing them to a target handler. Flushing occurs whenever the buffer
1028 is full, or when an event of a certain severity or greater is seen.
1029 """
1030 def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
1031 """
1032 Initialize the handler with the buffer size, the level at which
1033 flushing should occur and an optional target.
1034
1035 Note that without a target being set either here or via setTarget(),
1036 a MemoryHandler is no use to anyone!
1037 """
1038 BufferingHandler.__init__(self, capacity)
1039 self.flushLevel = flushLevel
1040 self.target = target
1041
1042 def shouldFlush(self, record):
1043 """
1044 Check for buffer full or a record at the flushLevel or higher.
1045 """
1046 return (len(self.buffer) >= self.capacity) or \
1047 (record.levelno >= self.flushLevel)
1048
1049 def setTarget(self, target):
1050 """
1051 Set the target handler for this handler.
1052 """
1053 self.target = target
1054
1055 def flush(self):
1056 """
1057 For a MemoryHandler, flushing means just sending the buffered
1058 records to the target, if there is one. Override if you want
1059 different behaviour.
1060 """
1061 if self.target:
1062 for record in self.buffer:
1063 self.target.handle(record)
1064 self.buffer = []
1065
1066 def close(self):
1067 """
1068 Flush, set the target to None and lose the buffer.
1069 """
1070 self.flush()
1071 self.target = None
Vinay Sajip48cfe382004-02-20 13:17:27 +00001072 BufferingHandler.close(self)