blob: 6225f17f9678cea81a0b42cd03d6e2467031b9a7 [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
Vinay Sajip17c52d82004-07-03 11:48:34 +000030import sys, logging, socket, types, os, string, cPickle, struct, time, glob
Guido van Rossum57102f82002-11-13 16:15:58 +000031
Vinay Sajip4600f112005-03-13 09:56:36 +000032try:
33 import codecs
34except ImportError:
35 codecs = None
36
Guido van Rossum57102f82002-11-13 16:15:58 +000037#
38# Some constants...
39#
40
41DEFAULT_TCP_LOGGING_PORT = 9020
42DEFAULT_UDP_LOGGING_PORT = 9021
43DEFAULT_HTTP_LOGGING_PORT = 9022
44DEFAULT_SOAP_LOGGING_PORT = 9023
45SYSLOG_UDP_PORT = 514
46
Vinay Sajip17c52d82004-07-03 11:48:34 +000047class BaseRotatingHandler(logging.FileHandler):
48 """
49 Base class for handlers that rotate log files at a certain point.
50 Not meant to be instantiated directly. Instead, use RotatingFileHandler
51 or TimedRotatingFileHandler.
52 """
Vinay Sajip4600f112005-03-13 09:56:36 +000053 def __init__(self, filename, mode, encoding=None):
Vinay Sajip17c52d82004-07-03 11:48:34 +000054 """
55 Use the specified filename for streamed logging
56 """
Vinay Sajip4600f112005-03-13 09:56:36 +000057 if codecs is None:
58 encoding = None
59 logging.FileHandler.__init__(self, filename, mode, encoding)
60 self.mode = mode
61 self.encoding = encoding
Guido van Rossum57102f82002-11-13 16:15:58 +000062
Vinay Sajip17c52d82004-07-03 11:48:34 +000063 def emit(self, record):
64 """
65 Emit a record.
66
67 Output the record to the file, catering for rollover as described
68 in doRollover().
69 """
Vinay Sajip3970c112004-07-08 10:24:04 +000070 try:
71 if self.shouldRollover(record):
72 self.doRollover()
73 logging.FileHandler.emit(self, record)
Vinay Sajip85c19092005-10-31 13:14:19 +000074 except (KeyboardInterrupt, SystemExit):
75 raise
Vinay Sajip3970c112004-07-08 10:24:04 +000076 except:
77 self.handleError(record)
Vinay Sajip17c52d82004-07-03 11:48:34 +000078
79class RotatingFileHandler(BaseRotatingHandler):
80 """
81 Handler for logging to a set of files, which switches from one file
82 to the next when the current file reaches a certain size.
83 """
Vinay Sajip4600f112005-03-13 09:56:36 +000084 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None):
Guido van Rossum57102f82002-11-13 16:15:58 +000085 """
86 Open the specified file and use it as the stream for logging.
87
88 By default, the file grows indefinitely. You can specify particular
89 values of maxBytes and backupCount to allow the file to rollover at
90 a predetermined size.
91
92 Rollover occurs whenever the current log file is nearly maxBytes in
93 length. If backupCount is >= 1, the system will successively create
94 new files with the same pathname as the base file, but with extensions
95 ".1", ".2" etc. appended to it. For example, with a backupCount of 5
96 and a base file name of "app.log", you would get "app.log",
97 "app.log.1", "app.log.2", ... through to "app.log.5". The file being
98 written to is always "app.log" - when it gets filled up, it is closed
99 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
100 exist, then they are renamed to "app.log.2", "app.log.3" etc.
101 respectively.
102
103 If maxBytes is zero, rollover never occurs.
104 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000105 if maxBytes > 0:
Vinay Sajip4600f112005-03-13 09:56:36 +0000106 mode = 'a' # doesn't make sense otherwise!
107 BaseRotatingHandler.__init__(self, filename, mode, encoding)
Guido van Rossum57102f82002-11-13 16:15:58 +0000108 self.maxBytes = maxBytes
109 self.backupCount = backupCount
Guido van Rossum57102f82002-11-13 16:15:58 +0000110
111 def doRollover(self):
112 """
113 Do a rollover, as described in __init__().
114 """
115
116 self.stream.close()
117 if self.backupCount > 0:
118 for i in range(self.backupCount - 1, 0, -1):
119 sfn = "%s.%d" % (self.baseFilename, i)
120 dfn = "%s.%d" % (self.baseFilename, i + 1)
121 if os.path.exists(sfn):
122 #print "%s -> %s" % (sfn, dfn)
123 if os.path.exists(dfn):
124 os.remove(dfn)
125 os.rename(sfn, dfn)
126 dfn = self.baseFilename + ".1"
127 if os.path.exists(dfn):
128 os.remove(dfn)
129 os.rename(self.baseFilename, dfn)
130 #print "%s -> %s" % (self.baseFilename, dfn)
Vinay Sajip4600f112005-03-13 09:56:36 +0000131 if self.encoding:
132 self.stream = codecs.open(self.baseFilename, 'w', self.encoding)
133 else:
134 self.stream = open(self.baseFilename, 'w')
Guido van Rossum57102f82002-11-13 16:15:58 +0000135
Vinay Sajip17c52d82004-07-03 11:48:34 +0000136 def shouldRollover(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000137 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000138 Determine if rollover should occur.
Guido van Rossum57102f82002-11-13 16:15:58 +0000139
Vinay Sajip17c52d82004-07-03 11:48:34 +0000140 Basically, see if the supplied record would cause the file to exceed
141 the size limit we have.
Guido van Rossum57102f82002-11-13 16:15:58 +0000142 """
143 if self.maxBytes > 0: # are we rolling over?
144 msg = "%s\n" % self.format(record)
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000145 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
Guido van Rossum57102f82002-11-13 16:15:58 +0000146 if self.stream.tell() + len(msg) >= self.maxBytes:
Vinay Sajip17c52d82004-07-03 11:48:34 +0000147 return 1
148 return 0
Guido van Rossum57102f82002-11-13 16:15:58 +0000149
Vinay Sajip17c52d82004-07-03 11:48:34 +0000150class TimedRotatingFileHandler(BaseRotatingHandler):
151 """
152 Handler for logging to a file, rotating the log file at certain timed
153 intervals.
154
155 If backupCount is > 0, when rollover is done, no more than backupCount
156 files are kept - the oldest ones are deleted.
157 """
Vinay Sajip4600f112005-03-13 09:56:36 +0000158 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None):
159 BaseRotatingHandler.__init__(self, filename, 'a', encoding)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000160 self.when = string.upper(when)
161 self.backupCount = backupCount
162 # Calculate the real rollover interval, which is just the number of
163 # seconds between rollovers. Also set the filename suffix used when
164 # a rollover occurs. Current 'when' events supported:
165 # S - Seconds
166 # M - Minutes
167 # H - Hours
168 # D - Days
169 # midnight - roll over at midnight
170 # W{0-6} - roll over on a certain day; 0 - Monday
171 #
172 # Case of the 'when' specifier is not important; lower or upper case
173 # will work.
174 currentTime = int(time.time())
175 if self.when == 'S':
176 self.interval = 1 # one second
177 self.suffix = "%Y-%m-%d_%H-%M-%S"
178 elif self.when == 'M':
179 self.interval = 60 # one minute
180 self.suffix = "%Y-%m-%d_%H-%M"
181 elif self.when == 'H':
182 self.interval = 60 * 60 # one hour
183 self.suffix = "%Y-%m-%d_%H"
184 elif self.when == 'D' or self.when == 'MIDNIGHT':
185 self.interval = 60 * 60 * 24 # one day
186 self.suffix = "%Y-%m-%d"
187 elif self.when.startswith('W'):
188 self.interval = 60 * 60 * 24 * 7 # one week
189 if len(self.when) != 2:
190 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
191 if self.when[1] < '0' or self.when[1] > '6':
192 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
193 self.dayOfWeek = int(self.when[1])
194 self.suffix = "%Y-%m-%d"
195 else:
196 raise ValueError("Invalid rollover interval specified: %s" % self.when)
197
Vinay Sajipe7d40662004-10-03 19:12:07 +0000198 self.interval = self.interval * interval # multiply by units requested
Vinay Sajip17c52d82004-07-03 11:48:34 +0000199 self.rolloverAt = currentTime + self.interval
200
201 # If we are rolling over at midnight or weekly, then the interval is already known.
202 # What we need to figure out is WHEN the next interval is. In other words,
203 # if you are rolling over at midnight, then your base interval is 1 day,
204 # but you want to start that one day clock at midnight, not now. So, we
205 # have to fudge the rolloverAt value in order to trigger the first rollover
206 # at the right time. After that, the regular interval will take care of
207 # the rest. Note that this code doesn't care about leap seconds. :)
208 if self.when == 'MIDNIGHT' or self.when.startswith('W'):
209 # This could be done with less code, but I wanted it to be clear
210 t = time.localtime(currentTime)
211 currentHour = t[3]
212 currentMinute = t[4]
213 currentSecond = t[5]
214 # r is the number of seconds left between now and midnight
Vinay Sajip74a83e92006-01-16 09:08:06 +0000215 if (currentMinute == 0) and (currentSecond == 0):
216 r = (24 - currentHour) * 60 * 60 # number of hours in seconds
217 else:
218 r = (23 - currentHour) * 60 * 60
219 r = r + (59 - currentMinute) * 60 # plus the number of minutes (in secs)
220 r = r + (60 - currentSecond) # plus the number of seconds
Vinay Sajip17c52d82004-07-03 11:48:34 +0000221 self.rolloverAt = currentTime + r
222 # If we are rolling over on a certain day, add in the number of days until
223 # the next rollover, but offset by 1 since we just calculated the time
224 # until the next day starts. There are three cases:
225 # Case 1) The day to rollover is today; in this case, do nothing
226 # Case 2) The day to rollover is further in the interval (i.e., today is
227 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
228 # next rollover is simply 6 - 2 - 1, or 3.
229 # Case 3) The day to rollover is behind us in the interval (i.e., today
230 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
231 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
232 # number of days left in the current week (1) plus the number
233 # of days in the next week until the rollover day (3).
234 if when.startswith('W'):
235 day = t[6] # 0 is Monday
236 if day > self.dayOfWeek:
237 daysToWait = (day - self.dayOfWeek) - 1
Vinay Sajipe7d40662004-10-03 19:12:07 +0000238 self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
Vinay Sajip17c52d82004-07-03 11:48:34 +0000239 if day < self.dayOfWeek:
240 daysToWait = (6 - self.dayOfWeek) + day
Vinay Sajipe7d40662004-10-03 19:12:07 +0000241 self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
Vinay Sajip17c52d82004-07-03 11:48:34 +0000242
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000243 #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000244
245 def shouldRollover(self, record):
246 """
247 Determine if rollover should occur
248
249 record is not used, as we are just comparing times, but it is needed so
250 the method siguratures are the same
251 """
252 t = int(time.time())
253 if t >= self.rolloverAt:
254 return 1
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000255 #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000256 return 0
257
258 def doRollover(self):
259 """
260 do a rollover; in this case, a date/time stamp is appended to the filename
261 when the rollover happens. However, you want the file to be named for the
262 start of the interval, not the current time. If there is a backup count,
263 then we have to get a list of matching filenames, sort them and remove
264 the one with the oldest suffix.
265 """
266 self.stream.close()
267 # get the time that this sequence started at and make it a TimeTuple
268 t = self.rolloverAt - self.interval
269 timeTuple = time.localtime(t)
270 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
271 if os.path.exists(dfn):
272 os.remove(dfn)
273 os.rename(self.baseFilename, dfn)
274 if self.backupCount > 0:
275 # find the oldest log file and delete it
276 s = glob.glob(self.baseFilename + ".20*")
277 if len(s) > self.backupCount:
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000278 s.sort()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000279 os.remove(s[0])
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000280 #print "%s -> %s" % (self.baseFilename, dfn)
Vinay Sajip4600f112005-03-13 09:56:36 +0000281 if self.encoding:
282 self.stream = codecs.open(self.baseFilename, 'w', self.encoding)
283 else:
284 self.stream = open(self.baseFilename, 'w')
Vinay Sajip17c52d82004-07-03 11:48:34 +0000285 self.rolloverAt = int(time.time()) + self.interval
Guido van Rossum57102f82002-11-13 16:15:58 +0000286
287class SocketHandler(logging.Handler):
288 """
289 A handler class which writes logging records, in pickle format, to
290 a streaming socket. The socket is kept open across logging calls.
291 If the peer resets it, an attempt is made to reconnect on the next call.
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000292 The pickle which is sent is that of the LogRecord's attribute dictionary
293 (__dict__), so that the receiver does not need to have the logging module
294 installed in order to process the logging event.
295
296 To unpickle the record at the receiving end into a LogRecord, use the
297 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000298 """
299
300 def __init__(self, host, port):
301 """
302 Initializes the handler with a specific host address and port.
303
304 The attribute 'closeOnError' is set to 1 - which means that if
305 a socket error occurs, the socket is silently closed and then
306 reopened on the next logging call.
307 """
308 logging.Handler.__init__(self)
309 self.host = host
310 self.port = port
311 self.sock = None
312 self.closeOnError = 0
Vinay Sajip48cfe382004-02-20 13:17:27 +0000313 self.retryTime = None
314 #
315 # Exponential backoff parameters.
316 #
317 self.retryStart = 1.0
318 self.retryMax = 30.0
319 self.retryFactor = 2.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000320
321 def makeSocket(self):
322 """
323 A factory method which allows subclasses to define the precise
324 type of socket they want.
325 """
326 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
327 s.connect((self.host, self.port))
328 return s
329
Vinay Sajip48cfe382004-02-20 13:17:27 +0000330 def createSocket(self):
331 """
332 Try to create a socket, using an exponential backoff with
333 a max retry time. Thanks to Robert Olson for the original patch
334 (SF #815911) which has been slightly refactored.
335 """
336 now = time.time()
337 # Either retryTime is None, in which case this
338 # is the first time back after a disconnect, or
339 # we've waited long enough.
340 if self.retryTime is None:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000341 attempt = 1
Vinay Sajip48cfe382004-02-20 13:17:27 +0000342 else:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000343 attempt = (now >= self.retryTime)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000344 if attempt:
345 try:
346 self.sock = self.makeSocket()
347 self.retryTime = None # next time, no delay before trying
348 except:
349 #Creation failed, so set the retry time and return.
350 if self.retryTime is None:
351 self.retryPeriod = self.retryStart
352 else:
353 self.retryPeriod = self.retryPeriod * self.retryFactor
354 if self.retryPeriod > self.retryMax:
355 self.retryPeriod = self.retryMax
356 self.retryTime = now + self.retryPeriod
357
Guido van Rossum57102f82002-11-13 16:15:58 +0000358 def send(self, s):
359 """
360 Send a pickled string to the socket.
361
362 This function allows for partial sends which can happen when the
363 network is busy.
364 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000365 if self.sock is None:
366 self.createSocket()
367 #self.sock can be None either because we haven't reached the retry
368 #time yet, or because we have reached the retry time and retried,
369 #but are still unable to connect.
370 if self.sock:
371 try:
372 if hasattr(self.sock, "sendall"):
373 self.sock.sendall(s)
374 else:
375 sentsofar = 0
376 left = len(s)
377 while left > 0:
378 sent = self.sock.send(s[sentsofar:])
379 sentsofar = sentsofar + sent
380 left = left - sent
381 except socket.error:
382 self.sock.close()
383 self.sock = None # so we can call createSocket next time
Guido van Rossum57102f82002-11-13 16:15:58 +0000384
385 def makePickle(self, record):
386 """
387 Pickles the record in binary format with a length prefix, and
388 returns it ready for transmission across the socket.
389 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000390 ei = record.exc_info
391 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000392 dummy = self.format(record) # just to get traceback text into record.exc_text
393 record.exc_info = None # to avoid Unpickleable error
Guido van Rossum57102f82002-11-13 16:15:58 +0000394 s = cPickle.dumps(record.__dict__, 1)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000395 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000396 record.exc_info = ei # for next handler
Guido van Rossum57102f82002-11-13 16:15:58 +0000397 slen = struct.pack(">L", len(s))
398 return slen + s
399
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000400 def handleError(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000401 """
402 Handle an error during logging.
403
404 An error has occurred during logging. Most likely cause -
405 connection lost. Close the socket so that we can retry on the
406 next event.
407 """
408 if self.closeOnError and self.sock:
409 self.sock.close()
410 self.sock = None #try to reconnect next time
411 else:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000412 logging.Handler.handleError(self, record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000413
414 def emit(self, record):
415 """
416 Emit a record.
417
418 Pickles the record and writes it to the socket in binary format.
419 If there is an error with the socket, silently drop the packet.
420 If there was a problem with the socket, re-establishes the
421 socket.
422 """
423 try:
424 s = self.makePickle(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000425 self.send(s)
Vinay Sajip85c19092005-10-31 13:14:19 +0000426 except (KeyboardInterrupt, SystemExit):
427 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000428 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000429 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000430
431 def close(self):
432 """
433 Closes the socket.
434 """
435 if self.sock:
436 self.sock.close()
437 self.sock = None
Vinay Sajip48cfe382004-02-20 13:17:27 +0000438 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000439
440class DatagramHandler(SocketHandler):
441 """
442 A handler class which writes logging records, in pickle format, to
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000443 a datagram socket. The pickle which is sent is that of the LogRecord's
444 attribute dictionary (__dict__), so that the receiver does not need to
445 have the logging module installed in order to process the logging event.
446
447 To unpickle the record at the receiving end into a LogRecord, use the
448 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000449
450 """
451 def __init__(self, host, port):
452 """
453 Initializes the handler with a specific host address and port.
454 """
455 SocketHandler.__init__(self, host, port)
456 self.closeOnError = 0
457
458 def makeSocket(self):
459 """
460 The factory method of SocketHandler is here overridden to create
461 a UDP socket (SOCK_DGRAM).
462 """
463 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
464 return s
465
466 def send(self, s):
467 """
468 Send a pickled string to a socket.
469
470 This function no longer allows for partial sends which can happen
471 when the network is busy - UDP does not guarantee delivery and
472 can deliver packets out of sequence.
473 """
Vinay Sajipfb154172004-08-24 09:36:23 +0000474 if self.sock is None:
475 self.createSocket()
Guido van Rossum57102f82002-11-13 16:15:58 +0000476 self.sock.sendto(s, (self.host, self.port))
477
478class SysLogHandler(logging.Handler):
479 """
480 A handler class which sends formatted logging records to a syslog
481 server. Based on Sam Rushing's syslog module:
482 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
483 Contributed by Nicolas Untz (after which minor refactoring changes
484 have been made).
485 """
486
487 # from <linux/sys/syslog.h>:
488 # ======================================================================
489 # priorities/facilities are encoded into a single 32-bit quantity, where
490 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
491 # facility (0-big number). Both the priorities and the facilities map
492 # roughly one-to-one to strings in the syslogd(8) source code. This
493 # mapping is included in this file.
494 #
495 # priorities (these are ordered)
496
497 LOG_EMERG = 0 # system is unusable
498 LOG_ALERT = 1 # action must be taken immediately
499 LOG_CRIT = 2 # critical conditions
500 LOG_ERR = 3 # error conditions
501 LOG_WARNING = 4 # warning conditions
502 LOG_NOTICE = 5 # normal but significant condition
503 LOG_INFO = 6 # informational
504 LOG_DEBUG = 7 # debug-level messages
505
506 # facility codes
507 LOG_KERN = 0 # kernel messages
508 LOG_USER = 1 # random user-level messages
509 LOG_MAIL = 2 # mail system
510 LOG_DAEMON = 3 # system daemons
511 LOG_AUTH = 4 # security/authorization messages
512 LOG_SYSLOG = 5 # messages generated internally by syslogd
513 LOG_LPR = 6 # line printer subsystem
514 LOG_NEWS = 7 # network news subsystem
515 LOG_UUCP = 8 # UUCP subsystem
516 LOG_CRON = 9 # clock daemon
517 LOG_AUTHPRIV = 10 # security/authorization messages (private)
518
519 # other codes through 15 reserved for system use
520 LOG_LOCAL0 = 16 # reserved for local use
521 LOG_LOCAL1 = 17 # reserved for local use
522 LOG_LOCAL2 = 18 # reserved for local use
523 LOG_LOCAL3 = 19 # reserved for local use
524 LOG_LOCAL4 = 20 # reserved for local use
525 LOG_LOCAL5 = 21 # reserved for local use
526 LOG_LOCAL6 = 22 # reserved for local use
527 LOG_LOCAL7 = 23 # reserved for local use
528
529 priority_names = {
530 "alert": LOG_ALERT,
531 "crit": LOG_CRIT,
532 "critical": LOG_CRIT,
533 "debug": LOG_DEBUG,
534 "emerg": LOG_EMERG,
535 "err": LOG_ERR,
536 "error": LOG_ERR, # DEPRECATED
537 "info": LOG_INFO,
538 "notice": LOG_NOTICE,
539 "panic": LOG_EMERG, # DEPRECATED
540 "warn": LOG_WARNING, # DEPRECATED
541 "warning": LOG_WARNING,
542 }
543
544 facility_names = {
545 "auth": LOG_AUTH,
546 "authpriv": LOG_AUTHPRIV,
547 "cron": LOG_CRON,
548 "daemon": LOG_DAEMON,
549 "kern": LOG_KERN,
550 "lpr": LOG_LPR,
551 "mail": LOG_MAIL,
552 "news": LOG_NEWS,
553 "security": LOG_AUTH, # DEPRECATED
554 "syslog": LOG_SYSLOG,
555 "user": LOG_USER,
556 "uucp": LOG_UUCP,
557 "local0": LOG_LOCAL0,
558 "local1": LOG_LOCAL1,
559 "local2": LOG_LOCAL2,
560 "local3": LOG_LOCAL3,
561 "local4": LOG_LOCAL4,
562 "local5": LOG_LOCAL5,
563 "local6": LOG_LOCAL6,
564 "local7": LOG_LOCAL7,
565 }
566
567 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER):
568 """
569 Initialize a handler.
570
571 If address is specified as a string, UNIX socket is used.
572 If facility is not specified, LOG_USER is used.
573 """
574 logging.Handler.__init__(self)
575
576 self.address = address
577 self.facility = facility
578 if type(address) == types.StringType:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000579 self._connect_unixsocket(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000580 self.unixsocket = 1
581 else:
582 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
583 self.unixsocket = 0
584
585 self.formatter = None
586
Vinay Sajipa1974c12005-01-13 08:23:56 +0000587 def _connect_unixsocket(self, address):
588 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
589 # syslog may require either DGRAM or STREAM sockets
590 try:
591 self.socket.connect(address)
592 except socket.error:
593 self.socket.close()
594 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Vinay Sajip8b6b53f2005-11-09 13:55:13 +0000595 self.socket.connect(address)
Vinay Sajipa1974c12005-01-13 08:23:56 +0000596
Guido van Rossum57102f82002-11-13 16:15:58 +0000597 # curious: when talking to the unix-domain '/dev/log' socket, a
598 # zero-terminator seems to be required. this string is placed
599 # into a class variable so that it can be overridden if
600 # necessary.
601 log_format_string = '<%d>%s\000'
602
603 def encodePriority (self, facility, priority):
604 """
605 Encode the facility and priority. You can pass in strings or
606 integers - if strings are passed, the facility_names and
607 priority_names mapping dictionaries are used to convert them to
608 integers.
609 """
610 if type(facility) == types.StringType:
611 facility = self.facility_names[facility]
612 if type(priority) == types.StringType:
613 priority = self.priority_names[priority]
614 return (facility << 3) | priority
615
616 def close (self):
617 """
618 Closes the socket.
619 """
620 if self.unixsocket:
621 self.socket.close()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000622 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000623
624 def emit(self, record):
625 """
626 Emit a record.
627
628 The record is formatted, and then sent to the syslog server. If
629 exception information is present, it is NOT sent to the server.
630 """
631 msg = self.format(record)
632 """
633 We need to convert record level to lowercase, maybe this will
634 change in the future.
635 """
636 msg = self.log_format_string % (
637 self.encodePriority(self.facility,
638 string.lower(record.levelname)),
639 msg)
640 try:
641 if self.unixsocket:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000642 try:
643 self.socket.send(msg)
644 except socket.error:
645 self._connect_unixsocket(self.address)
646 self.socket.send(msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000647 else:
648 self.socket.sendto(msg, self.address)
Vinay Sajip85c19092005-10-31 13:14:19 +0000649 except (KeyboardInterrupt, SystemExit):
650 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000651 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000652 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000653
654class SMTPHandler(logging.Handler):
655 """
656 A handler class which sends an SMTP email for each logging event.
657 """
658 def __init__(self, mailhost, fromaddr, toaddrs, subject):
659 """
660 Initialize the handler.
661
662 Initialize the instance with the from and to addresses and subject
663 line of the email. To specify a non-standard SMTP port, use the
664 (host, port) tuple format for the mailhost argument.
665 """
666 logging.Handler.__init__(self)
667 if type(mailhost) == types.TupleType:
668 host, port = mailhost
669 self.mailhost = host
670 self.mailport = port
671 else:
672 self.mailhost = mailhost
673 self.mailport = None
674 self.fromaddr = fromaddr
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000675 if type(toaddrs) == types.StringType:
676 toaddrs = [toaddrs]
Guido van Rossum57102f82002-11-13 16:15:58 +0000677 self.toaddrs = toaddrs
678 self.subject = subject
679
680 def getSubject(self, record):
681 """
682 Determine the subject for the email.
683
684 If you want to specify a subject line which is record-dependent,
685 override this method.
686 """
687 return self.subject
688
Vinay Sajipe7d40662004-10-03 19:12:07 +0000689 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
690
691 monthname = [None,
692 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
693 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
694
695 def date_time(self):
696 """
697 Return the current date and time formatted for a MIME header.
698 Needed for Python 1.5.2 (no email package available)
699 """
700 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
701 s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
702 self.weekdayname[wd],
703 day, self.monthname[month], year,
704 hh, mm, ss)
705 return s
706
Guido van Rossum57102f82002-11-13 16:15:58 +0000707 def emit(self, record):
708 """
709 Emit a record.
710
711 Format the record and send it to the specified addressees.
712 """
713 try:
714 import smtplib
Vinay Sajipe7d40662004-10-03 19:12:07 +0000715 try:
716 from email.Utils import formatdate
717 except:
718 formatdate = self.date_time
Guido van Rossum57102f82002-11-13 16:15:58 +0000719 port = self.mailport
720 if not port:
721 port = smtplib.SMTP_PORT
722 smtp = smtplib.SMTP(self.mailhost, port)
723 msg = self.format(record)
Neal Norwitzf297bd12003-04-23 03:49:43 +0000724 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 +0000725 self.fromaddr,
726 string.join(self.toaddrs, ","),
Neal Norwitzf297bd12003-04-23 03:49:43 +0000727 self.getSubject(record),
Martin v. Löwis318a12e2004-08-18 12:27:40 +0000728 formatdate(), msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000729 smtp.sendmail(self.fromaddr, self.toaddrs, msg)
730 smtp.quit()
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000731 except (KeyboardInterrupt, SystemExit):
732 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000733 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000734 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000735
736class NTEventLogHandler(logging.Handler):
737 """
738 A handler class which sends events to the NT Event Log. Adds a
739 registry entry for the specified application name. If no dllname is
740 provided, win32service.pyd (which contains some basic message
741 placeholders) is used. Note that use of these placeholders will make
742 your event logs big, as the entire message source is held in the log.
743 If you want slimmer logs, you have to pass in the name of your own DLL
744 which contains the message definitions you want to use in the event log.
745 """
746 def __init__(self, appname, dllname=None, logtype="Application"):
747 logging.Handler.__init__(self)
748 try:
749 import win32evtlogutil, win32evtlog
750 self.appname = appname
751 self._welu = win32evtlogutil
752 if not dllname:
753 dllname = os.path.split(self._welu.__file__)
754 dllname = os.path.split(dllname[0])
755 dllname = os.path.join(dllname[0], r'win32service.pyd')
756 self.dllname = dllname
757 self.logtype = logtype
758 self._welu.AddSourceToRegistry(appname, dllname, logtype)
759 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
760 self.typemap = {
761 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
762 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000763 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
Guido van Rossum57102f82002-11-13 16:15:58 +0000764 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
765 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
766 }
767 except ImportError:
768 print "The Python Win32 extensions for NT (service, event "\
769 "logging) appear not to be available."
770 self._welu = None
771
772 def getMessageID(self, record):
773 """
774 Return the message ID for the event record. If you are using your
775 own messages, you could do this by having the msg passed to the
776 logger being an ID rather than a formatting string. Then, in here,
777 you could use a dictionary lookup to get the message ID. This
778 version returns 1, which is the base message ID in win32service.pyd.
779 """
780 return 1
781
782 def getEventCategory(self, record):
783 """
784 Return the event category for the record.
785
786 Override this if you want to specify your own categories. This version
787 returns 0.
788 """
789 return 0
790
791 def getEventType(self, record):
792 """
793 Return the event type for the record.
794
795 Override this if you want to specify your own types. This version does
796 a mapping using the handler's typemap attribute, which is set up in
797 __init__() to a dictionary which contains mappings for DEBUG, INFO,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000798 WARNING, ERROR and CRITICAL. If you are using your own levels you will
Guido van Rossum57102f82002-11-13 16:15:58 +0000799 either need to override this method or place a suitable dictionary in
800 the handler's typemap attribute.
801 """
802 return self.typemap.get(record.levelno, self.deftype)
803
804 def emit(self, record):
805 """
806 Emit a record.
807
808 Determine the message ID, event category and event type. Then
809 log the message in the NT event log.
810 """
811 if self._welu:
812 try:
813 id = self.getMessageID(record)
814 cat = self.getEventCategory(record)
815 type = self.getEventType(record)
816 msg = self.format(record)
817 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000818 except (KeyboardInterrupt, SystemExit):
819 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000820 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000821 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000822
823 def close(self):
824 """
825 Clean up this handler.
826
827 You can remove the application name from the registry as a
828 source of event log entries. However, if you do this, you will
829 not be able to see the events as you intended in the Event Log
830 Viewer - it needs to be able to access the registry to get the
831 DLL name.
832 """
833 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000834 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000835
836class HTTPHandler(logging.Handler):
837 """
838 A class which sends records to a Web server, using either GET or
839 POST semantics.
840 """
841 def __init__(self, host, url, method="GET"):
842 """
843 Initialize the instance with the host, the request URL, and the method
844 ("GET" or "POST")
845 """
846 logging.Handler.__init__(self)
847 method = string.upper(method)
848 if method not in ["GET", "POST"]:
849 raise ValueError, "method must be GET or POST"
850 self.host = host
851 self.url = url
852 self.method = method
853
Neal Norwitzf297bd12003-04-23 03:49:43 +0000854 def mapLogRecord(self, record):
855 """
856 Default implementation of mapping the log record into a dict
Vinay Sajip48cfe382004-02-20 13:17:27 +0000857 that is sent as the CGI data. Overwrite in your class.
Neal Norwitzf297bd12003-04-23 03:49:43 +0000858 Contributed by Franz Glasner.
859 """
860 return record.__dict__
861
Guido van Rossum57102f82002-11-13 16:15:58 +0000862 def emit(self, record):
863 """
864 Emit a record.
865
866 Send the record to the Web server as an URL-encoded dictionary
867 """
868 try:
869 import httplib, urllib
Vinay Sajipb7935062005-10-11 13:15:31 +0000870 host = self.host
871 h = httplib.HTTP(host)
Guido van Rossum57102f82002-11-13 16:15:58 +0000872 url = self.url
Neal Norwitzf297bd12003-04-23 03:49:43 +0000873 data = urllib.urlencode(self.mapLogRecord(record))
Guido van Rossum57102f82002-11-13 16:15:58 +0000874 if self.method == "GET":
875 if (string.find(url, '?') >= 0):
876 sep = '&'
877 else:
878 sep = '?'
879 url = url + "%c%s" % (sep, data)
880 h.putrequest(self.method, url)
Vinay Sajipb7935062005-10-11 13:15:31 +0000881 # support multiple hosts on one IP address...
882 # need to strip optional :port from host, if present
883 i = string.find(host, ":")
884 if i >= 0:
885 host = host[:i]
886 h.putheader("Host", host)
Guido van Rossum57102f82002-11-13 16:15:58 +0000887 if self.method == "POST":
Vinay Sajipb7935062005-10-11 13:15:31 +0000888 h.putheader("Content-type",
889 "application/x-www-form-urlencoded")
Guido van Rossum57102f82002-11-13 16:15:58 +0000890 h.putheader("Content-length", str(len(data)))
891 h.endheaders()
892 if self.method == "POST":
893 h.send(data)
894 h.getreply() #can't do anything with the result
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000895 except (KeyboardInterrupt, SystemExit):
896 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000897 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000898 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000899
900class BufferingHandler(logging.Handler):
901 """
902 A handler class which buffers logging records in memory. Whenever each
903 record is added to the buffer, a check is made to see if the buffer should
904 be flushed. If it should, then flush() is expected to do what's needed.
905 """
906 def __init__(self, capacity):
907 """
908 Initialize the handler with the buffer size.
909 """
910 logging.Handler.__init__(self)
911 self.capacity = capacity
912 self.buffer = []
913
914 def shouldFlush(self, record):
915 """
916 Should the handler flush its buffer?
917
918 Returns true if the buffer is up to capacity. This method can be
919 overridden to implement custom flushing strategies.
920 """
921 return (len(self.buffer) >= self.capacity)
922
923 def emit(self, record):
924 """
925 Emit a record.
926
927 Append the record. If shouldFlush() tells us to, call flush() to process
928 the buffer.
929 """
930 self.buffer.append(record)
931 if self.shouldFlush(record):
932 self.flush()
933
934 def flush(self):
935 """
936 Override to implement custom flushing behaviour.
937
938 This version just zaps the buffer to empty.
939 """
940 self.buffer = []
941
Vinay Sajipf42d95e2004-02-21 22:14:34 +0000942 def close(self):
943 """
944 Close the handler.
945
946 This version just flushes and chains to the parent class' close().
947 """
948 self.flush()
949 logging.Handler.close(self)
950
Guido van Rossum57102f82002-11-13 16:15:58 +0000951class MemoryHandler(BufferingHandler):
952 """
953 A handler class which buffers logging records in memory, periodically
954 flushing them to a target handler. Flushing occurs whenever the buffer
955 is full, or when an event of a certain severity or greater is seen.
956 """
957 def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
958 """
959 Initialize the handler with the buffer size, the level at which
960 flushing should occur and an optional target.
961
962 Note that without a target being set either here or via setTarget(),
963 a MemoryHandler is no use to anyone!
964 """
965 BufferingHandler.__init__(self, capacity)
966 self.flushLevel = flushLevel
967 self.target = target
968
969 def shouldFlush(self, record):
970 """
971 Check for buffer full or a record at the flushLevel or higher.
972 """
973 return (len(self.buffer) >= self.capacity) or \
974 (record.levelno >= self.flushLevel)
975
976 def setTarget(self, target):
977 """
978 Set the target handler for this handler.
979 """
980 self.target = target
981
982 def flush(self):
983 """
984 For a MemoryHandler, flushing means just sending the buffered
985 records to the target, if there is one. Override if you want
986 different behaviour.
987 """
988 if self.target:
989 for record in self.buffer:
990 self.target.handle(record)
991 self.buffer = []
992
993 def close(self):
994 """
995 Flush, set the target to None and lose the buffer.
996 """
997 self.flush()
998 self.target = None
Vinay Sajip48cfe382004-02-20 13:17:27 +0000999 BufferingHandler.close(self)