blob: 29c398df1c52a55ff001a3bc8fd164a5d5dc79b6 [file] [log] [blame]
Thomas Woutersb2137042007-02-01 18:02:27 +00001# Copyright 2001-2007 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
Benjamin Petersonad9d48d2008-04-02 21:49:44 +000022Copyright (C) 2001-2008 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000023
24To use, simply 'import logging' and log away!
25"""
26
Benjamin Petersonad9d48d2008-04-02 21:49:44 +000027import logging, socket, os, pickle, struct, time, re
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +000028from stat import ST_DEV, ST_INO
Guido van Rossum57102f82002-11-13 16:15:58 +000029
Vinay Sajip4600f112005-03-13 09:56:36 +000030try:
31 import codecs
32except ImportError:
33 codecs = None
34
Guido van Rossum57102f82002-11-13 16:15:58 +000035#
36# Some constants...
37#
38
39DEFAULT_TCP_LOGGING_PORT = 9020
40DEFAULT_UDP_LOGGING_PORT = 9021
41DEFAULT_HTTP_LOGGING_PORT = 9022
42DEFAULT_SOAP_LOGGING_PORT = 9023
43SYSLOG_UDP_PORT = 514
44
Thomas Wouters477c8d52006-05-27 19:21:47 +000045_MIDNIGHT = 24 * 60 * 60 # number of seconds in a day
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 """
Christian Heimese7a15bb2008-01-24 16:21:45 +000053 def __init__(self, filename, mode, encoding=None, delay=0):
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
Christian Heimese7a15bb2008-01-24 16:21:45 +000059 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
Vinay Sajip4600f112005-03-13 09:56:36 +000060 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 """
Christian Heimese7a15bb2008-01-24 16:21:45 +000084 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0):
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!
Christian Heimese7a15bb2008-01-24 16:21:45 +0000107 BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
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)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000129 os.rename(self.baseFilename, dfn)
Guido van Rossum57102f82002-11-13 16:15:58 +0000130 #print "%s -> %s" % (self.baseFilename, dfn)
Thomas Woutersb2137042007-02-01 18:02:27 +0000131 self.mode = 'w'
132 self.stream = self._open()
Guido van Rossum57102f82002-11-13 16:15:58 +0000133
Vinay Sajip17c52d82004-07-03 11:48:34 +0000134 def shouldRollover(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000135 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000136 Determine if rollover should occur.
Guido van Rossum57102f82002-11-13 16:15:58 +0000137
Vinay Sajip17c52d82004-07-03 11:48:34 +0000138 Basically, see if the supplied record would cause the file to exceed
139 the size limit we have.
Guido van Rossum57102f82002-11-13 16:15:58 +0000140 """
141 if self.maxBytes > 0: # are we rolling over?
142 msg = "%s\n" % self.format(record)
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000143 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
Guido van Rossum57102f82002-11-13 16:15:58 +0000144 if self.stream.tell() + len(msg) >= self.maxBytes:
Vinay Sajip17c52d82004-07-03 11:48:34 +0000145 return 1
146 return 0
Guido van Rossum57102f82002-11-13 16:15:58 +0000147
Vinay Sajip17c52d82004-07-03 11:48:34 +0000148class TimedRotatingFileHandler(BaseRotatingHandler):
149 """
150 Handler for logging to a file, rotating the log file at certain timed
151 intervals.
152
153 If backupCount is > 0, when rollover is done, no more than backupCount
154 files are kept - the oldest ones are deleted.
155 """
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000156 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0):
Christian Heimese7a15bb2008-01-24 16:21:45 +0000157 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000158 self.when = when.upper()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000159 self.backupCount = backupCount
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000160 self.utc = utc
Vinay Sajip17c52d82004-07-03 11:48:34 +0000161 # Calculate the real rollover interval, which is just the number of
162 # seconds between rollovers. Also set the filename suffix used when
163 # a rollover occurs. Current 'when' events supported:
164 # S - Seconds
165 # M - Minutes
166 # H - Hours
167 # D - Days
168 # midnight - roll over at midnight
169 # W{0-6} - roll over on a certain day; 0 - Monday
170 #
171 # Case of the 'when' specifier is not important; lower or upper case
172 # will work.
173 currentTime = int(time.time())
174 if self.when == 'S':
175 self.interval = 1 # one second
176 self.suffix = "%Y-%m-%d_%H-%M-%S"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000177 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000178 elif self.when == 'M':
179 self.interval = 60 # one minute
180 self.suffix = "%Y-%m-%d_%H-%M"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000181 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000182 elif self.when == 'H':
183 self.interval = 60 * 60 # one hour
184 self.suffix = "%Y-%m-%d_%H"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000185 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000186 elif self.when == 'D' or self.when == 'MIDNIGHT':
187 self.interval = 60 * 60 * 24 # one day
188 self.suffix = "%Y-%m-%d"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000189 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000190 elif self.when.startswith('W'):
191 self.interval = 60 * 60 * 24 * 7 # one week
192 if len(self.when) != 2:
193 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
194 if self.when[1] < '0' or self.when[1] > '6':
195 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
196 self.dayOfWeek = int(self.when[1])
197 self.suffix = "%Y-%m-%d"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000198 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000199 else:
200 raise ValueError("Invalid rollover interval specified: %s" % self.when)
201
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000202 self.extMatch = re.compile(self.extMatch)
Vinay Sajipe7d40662004-10-03 19:12:07 +0000203 self.interval = self.interval * interval # multiply by units requested
Vinay Sajip17c52d82004-07-03 11:48:34 +0000204 self.rolloverAt = currentTime + self.interval
205
206 # If we are rolling over at midnight or weekly, then the interval is already known.
207 # What we need to figure out is WHEN the next interval is. In other words,
208 # if you are rolling over at midnight, then your base interval is 1 day,
209 # but you want to start that one day clock at midnight, not now. So, we
210 # have to fudge the rolloverAt value in order to trigger the first rollover
211 # at the right time. After that, the regular interval will take care of
212 # the rest. Note that this code doesn't care about leap seconds. :)
213 if self.when == 'MIDNIGHT' or self.when.startswith('W'):
214 # This could be done with less code, but I wanted it to be clear
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000215 if utc:
216 t = time.gmtime(currentTime)
217 else:
218 t = time.localtime(currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000219 currentHour = t[3]
220 currentMinute = t[4]
221 currentSecond = t[5]
222 # r is the number of seconds left between now and midnight
Thomas Wouters477c8d52006-05-27 19:21:47 +0000223 r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
224 currentSecond)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000225 self.rolloverAt = currentTime + r
226 # If we are rolling over on a certain day, add in the number of days until
227 # the next rollover, but offset by 1 since we just calculated the time
228 # until the next day starts. There are three cases:
229 # Case 1) The day to rollover is today; in this case, do nothing
230 # Case 2) The day to rollover is further in the interval (i.e., today is
231 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
232 # next rollover is simply 6 - 2 - 1, or 3.
233 # Case 3) The day to rollover is behind us in the interval (i.e., today
234 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
235 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
236 # number of days left in the current week (1) plus the number
237 # of days in the next week until the rollover day (3).
Georg Brandl86def6c2008-01-21 20:36:10 +0000238 # The calculations described in 2) and 3) above need to have a day added.
239 # This is because the above time calculation takes us to midnight on this
240 # day, i.e. the start of the next day.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000241 if when.startswith('W'):
242 day = t[6] # 0 is Monday
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000243 if day != self.dayOfWeek:
244 if day < self.dayOfWeek:
Georg Brandl86def6c2008-01-21 20:36:10 +0000245 daysToWait = self.dayOfWeek - day
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000246 else:
Georg Brandl86def6c2008-01-21 20:36:10 +0000247 daysToWait = 6 - day + self.dayOfWeek + 1
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000248 newRolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000249 if not utc:
250 dstNow = t[-1]
251 dstAtRollover = time.localtime(newRolloverAt)[-1]
252 if dstNow != dstAtRollover:
253 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
254 newRolloverAt = newRolloverAt - 3600
255 else: # DST bows out before next rollover, so we need to add an hour
256 newRolloverAt = newRolloverAt + 3600
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000257 self.rolloverAt = newRolloverAt
Vinay Sajip17c52d82004-07-03 11:48:34 +0000258
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000259 #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000260
261 def shouldRollover(self, record):
262 """
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000263 Determine if rollover should occur.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000264
265 record is not used, as we are just comparing times, but it is needed so
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000266 the method signatures are the same
Vinay Sajip17c52d82004-07-03 11:48:34 +0000267 """
268 t = int(time.time())
269 if t >= self.rolloverAt:
270 return 1
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000271 #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000272 return 0
273
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000274 def getFilesToDelete(self):
275 """
276 Determine the files to delete when rolling over.
277
278 More specific than the earlier method, which just used glob.glob().
279 """
280 dirName, baseName = os.path.split(self.baseFilename)
281 fileNames = os.listdir(dirName)
282 result = []
283 prefix = baseName + "."
284 plen = len(prefix)
285 for fileName in fileNames:
286 if fileName[:plen] == prefix:
287 suffix = fileName[plen:]
288 if self.extMatch.match(suffix):
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000289 result.append(os.path.join(dirName, fileName))
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000290 result.sort()
291 if len(result) < self.backupCount:
292 result = []
293 else:
294 result = result[:len(result) - self.backupCount]
295 return result
296
Vinay Sajip17c52d82004-07-03 11:48:34 +0000297 def doRollover(self):
298 """
299 do a rollover; in this case, a date/time stamp is appended to the filename
300 when the rollover happens. However, you want the file to be named for the
301 start of the interval, not the current time. If there is a backup count,
302 then we have to get a list of matching filenames, sort them and remove
303 the one with the oldest suffix.
304 """
305 self.stream.close()
306 # get the time that this sequence started at and make it a TimeTuple
307 t = self.rolloverAt - self.interval
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000308 if self.utc:
309 timeTuple = time.gmtime(t)
310 else:
311 timeTuple = time.localtime(t)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000312 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
313 if os.path.exists(dfn):
314 os.remove(dfn)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000315 os.rename(self.baseFilename, dfn)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000316 if self.backupCount > 0:
317 # find the oldest log file and delete it
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000318 #s = glob.glob(self.baseFilename + ".20*")
319 #if len(s) > self.backupCount:
320 # s.sort()
321 # os.remove(s[0])
322 for s in self.getFilesToDelete():
323 os.remove(s)
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000324 #print "%s -> %s" % (self.baseFilename, dfn)
Thomas Woutersb2137042007-02-01 18:02:27 +0000325 self.mode = 'w'
326 self.stream = self._open()
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000327 newRolloverAt = self.rolloverAt + self.interval
328 currentTime = int(time.time())
329 while newRolloverAt <= currentTime:
330 newRolloverAt = newRolloverAt + self.interval
331 #If DST changes and midnight or weekly rollover, adjust for this.
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000332 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000333 dstNow = time.localtime(currentTime)[-1]
334 dstAtRollover = time.localtime(newRolloverAt)[-1]
335 if dstNow != dstAtRollover:
336 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
337 newRolloverAt = newRolloverAt - 3600
338 else: # DST bows out before next rollover, so we need to add an hour
339 newRolloverAt = newRolloverAt + 3600
340 self.rolloverAt = newRolloverAt
Guido van Rossum57102f82002-11-13 16:15:58 +0000341
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000342class WatchedFileHandler(logging.FileHandler):
343 """
344 A handler for logging to a file, which watches the file
345 to see if it has changed while in use. This can happen because of
346 usage of programs such as newsyslog and logrotate which perform
347 log file rotation. This handler, intended for use under Unix,
348 watches the file to see if it has changed since the last emit.
349 (A file has changed if its device or inode have changed.)
350 If it has changed, the old file stream is closed, and the file
351 opened to get a new stream.
352
353 This handler is not appropriate for use under Windows, because
354 under Windows open files cannot be moved or renamed - logging
355 opens the files with exclusive locks - and so there is no need
356 for such a handler. Furthermore, ST_INO is not supported under
357 Windows; stat always returns zero for this value.
358
359 This handler is based on a suggestion and patch by Chad J.
360 Schroeder.
361 """
Christian Heimese7a15bb2008-01-24 16:21:45 +0000362 def __init__(self, filename, mode='a', encoding=None, delay=0):
363 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
364 if not os.path.exists(self.baseFilename):
365 self.dev, self.ino = -1, -1
366 else:
367 stat = os.stat(self.baseFilename)
368 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000369
370 def emit(self, record):
371 """
372 Emit a record.
373
374 First check if the underlying file has changed, and if it
375 has, close the old stream and reopen the file to get the
376 current stream.
377 """
378 if not os.path.exists(self.baseFilename):
379 stat = None
380 changed = 1
381 else:
382 stat = os.stat(self.baseFilename)
383 changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
Christian Heimese7a15bb2008-01-24 16:21:45 +0000384 if changed and self.stream is not None:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000385 self.stream.flush()
386 self.stream.close()
387 self.stream = self._open()
388 if stat is None:
389 stat = os.stat(self.baseFilename)
390 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
391 logging.FileHandler.emit(self, record)
392
Guido van Rossum57102f82002-11-13 16:15:58 +0000393class SocketHandler(logging.Handler):
394 """
395 A handler class which writes logging records, in pickle format, to
396 a streaming socket. The socket is kept open across logging calls.
397 If the peer resets it, an attempt is made to reconnect on the next call.
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000398 The pickle which is sent is that of the LogRecord's attribute dictionary
399 (__dict__), so that the receiver does not need to have the logging module
400 installed in order to process the logging event.
401
402 To unpickle the record at the receiving end into a LogRecord, use the
403 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000404 """
405
406 def __init__(self, host, port):
407 """
408 Initializes the handler with a specific host address and port.
409
410 The attribute 'closeOnError' is set to 1 - which means that if
411 a socket error occurs, the socket is silently closed and then
412 reopened on the next logging call.
413 """
414 logging.Handler.__init__(self)
415 self.host = host
416 self.port = port
417 self.sock = None
418 self.closeOnError = 0
Vinay Sajip48cfe382004-02-20 13:17:27 +0000419 self.retryTime = None
420 #
421 # Exponential backoff parameters.
422 #
423 self.retryStart = 1.0
424 self.retryMax = 30.0
425 self.retryFactor = 2.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000426
Guido van Rossumd8faa362007-04-27 19:54:29 +0000427 def makeSocket(self, timeout=1):
Guido van Rossum57102f82002-11-13 16:15:58 +0000428 """
429 A factory method which allows subclasses to define the precise
430 type of socket they want.
431 """
432 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000433 if hasattr(s, 'settimeout'):
434 s.settimeout(timeout)
Guido van Rossum57102f82002-11-13 16:15:58 +0000435 s.connect((self.host, self.port))
436 return s
437
Vinay Sajip48cfe382004-02-20 13:17:27 +0000438 def createSocket(self):
439 """
440 Try to create a socket, using an exponential backoff with
441 a max retry time. Thanks to Robert Olson for the original patch
442 (SF #815911) which has been slightly refactored.
443 """
444 now = time.time()
445 # Either retryTime is None, in which case this
446 # is the first time back after a disconnect, or
447 # we've waited long enough.
448 if self.retryTime is None:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000449 attempt = 1
Vinay Sajip48cfe382004-02-20 13:17:27 +0000450 else:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000451 attempt = (now >= self.retryTime)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000452 if attempt:
453 try:
454 self.sock = self.makeSocket()
455 self.retryTime = None # next time, no delay before trying
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000456 except socket.error:
Vinay Sajip48cfe382004-02-20 13:17:27 +0000457 #Creation failed, so set the retry time and return.
458 if self.retryTime is None:
459 self.retryPeriod = self.retryStart
460 else:
461 self.retryPeriod = self.retryPeriod * self.retryFactor
462 if self.retryPeriod > self.retryMax:
463 self.retryPeriod = self.retryMax
464 self.retryTime = now + self.retryPeriod
465
Guido van Rossum57102f82002-11-13 16:15:58 +0000466 def send(self, s):
467 """
468 Send a pickled string to the socket.
469
470 This function allows for partial sends which can happen when the
471 network is busy.
472 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000473 if self.sock is None:
474 self.createSocket()
475 #self.sock can be None either because we haven't reached the retry
476 #time yet, or because we have reached the retry time and retried,
477 #but are still unable to connect.
478 if self.sock:
479 try:
480 if hasattr(self.sock, "sendall"):
481 self.sock.sendall(s)
482 else:
483 sentsofar = 0
484 left = len(s)
485 while left > 0:
486 sent = self.sock.send(s[sentsofar:])
487 sentsofar = sentsofar + sent
488 left = left - sent
489 except socket.error:
490 self.sock.close()
491 self.sock = None # so we can call createSocket next time
Guido van Rossum57102f82002-11-13 16:15:58 +0000492
493 def makePickle(self, record):
494 """
495 Pickles the record in binary format with a length prefix, and
496 returns it ready for transmission across the socket.
497 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000498 ei = record.exc_info
499 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000500 dummy = self.format(record) # just to get traceback text into record.exc_text
501 record.exc_info = None # to avoid Unpickleable error
Guido van Rossumba205d62006-08-17 08:57:26 +0000502 s = pickle.dumps(record.__dict__, 1)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000503 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000504 record.exc_info = ei # for next handler
Guido van Rossum57102f82002-11-13 16:15:58 +0000505 slen = struct.pack(">L", len(s))
506 return slen + s
507
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000508 def handleError(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000509 """
510 Handle an error during logging.
511
512 An error has occurred during logging. Most likely cause -
513 connection lost. Close the socket so that we can retry on the
514 next event.
515 """
516 if self.closeOnError and self.sock:
517 self.sock.close()
518 self.sock = None #try to reconnect next time
519 else:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000520 logging.Handler.handleError(self, record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000521
522 def emit(self, record):
523 """
524 Emit a record.
525
526 Pickles the record and writes it to the socket in binary format.
527 If there is an error with the socket, silently drop the packet.
528 If there was a problem with the socket, re-establishes the
529 socket.
530 """
531 try:
532 s = self.makePickle(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000533 self.send(s)
Vinay Sajip85c19092005-10-31 13:14:19 +0000534 except (KeyboardInterrupt, SystemExit):
535 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000536 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000537 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000538
539 def close(self):
540 """
541 Closes the socket.
542 """
543 if self.sock:
544 self.sock.close()
545 self.sock = None
Vinay Sajip48cfe382004-02-20 13:17:27 +0000546 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000547
548class DatagramHandler(SocketHandler):
549 """
550 A handler class which writes logging records, in pickle format, to
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000551 a datagram socket. The pickle which is sent is that of the LogRecord's
552 attribute dictionary (__dict__), so that the receiver does not need to
553 have the logging module installed in order to process the logging event.
554
555 To unpickle the record at the receiving end into a LogRecord, use the
556 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000557
558 """
559 def __init__(self, host, port):
560 """
561 Initializes the handler with a specific host address and port.
562 """
563 SocketHandler.__init__(self, host, port)
564 self.closeOnError = 0
565
566 def makeSocket(self):
567 """
568 The factory method of SocketHandler is here overridden to create
569 a UDP socket (SOCK_DGRAM).
570 """
571 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
572 return s
573
574 def send(self, s):
575 """
576 Send a pickled string to a socket.
577
578 This function no longer allows for partial sends which can happen
579 when the network is busy - UDP does not guarantee delivery and
580 can deliver packets out of sequence.
581 """
Vinay Sajipfb154172004-08-24 09:36:23 +0000582 if self.sock is None:
583 self.createSocket()
Guido van Rossum57102f82002-11-13 16:15:58 +0000584 self.sock.sendto(s, (self.host, self.port))
585
586class SysLogHandler(logging.Handler):
587 """
588 A handler class which sends formatted logging records to a syslog
589 server. Based on Sam Rushing's syslog module:
590 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
591 Contributed by Nicolas Untz (after which minor refactoring changes
592 have been made).
593 """
594
595 # from <linux/sys/syslog.h>:
596 # ======================================================================
597 # priorities/facilities are encoded into a single 32-bit quantity, where
598 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
599 # facility (0-big number). Both the priorities and the facilities map
600 # roughly one-to-one to strings in the syslogd(8) source code. This
601 # mapping is included in this file.
602 #
603 # priorities (these are ordered)
604
605 LOG_EMERG = 0 # system is unusable
606 LOG_ALERT = 1 # action must be taken immediately
607 LOG_CRIT = 2 # critical conditions
608 LOG_ERR = 3 # error conditions
609 LOG_WARNING = 4 # warning conditions
610 LOG_NOTICE = 5 # normal but significant condition
611 LOG_INFO = 6 # informational
612 LOG_DEBUG = 7 # debug-level messages
613
614 # facility codes
615 LOG_KERN = 0 # kernel messages
616 LOG_USER = 1 # random user-level messages
617 LOG_MAIL = 2 # mail system
618 LOG_DAEMON = 3 # system daemons
619 LOG_AUTH = 4 # security/authorization messages
620 LOG_SYSLOG = 5 # messages generated internally by syslogd
621 LOG_LPR = 6 # line printer subsystem
622 LOG_NEWS = 7 # network news subsystem
623 LOG_UUCP = 8 # UUCP subsystem
624 LOG_CRON = 9 # clock daemon
625 LOG_AUTHPRIV = 10 # security/authorization messages (private)
626
627 # other codes through 15 reserved for system use
628 LOG_LOCAL0 = 16 # reserved for local use
629 LOG_LOCAL1 = 17 # reserved for local use
630 LOG_LOCAL2 = 18 # reserved for local use
631 LOG_LOCAL3 = 19 # reserved for local use
632 LOG_LOCAL4 = 20 # reserved for local use
633 LOG_LOCAL5 = 21 # reserved for local use
634 LOG_LOCAL6 = 22 # reserved for local use
635 LOG_LOCAL7 = 23 # reserved for local use
636
637 priority_names = {
638 "alert": LOG_ALERT,
639 "crit": LOG_CRIT,
640 "critical": LOG_CRIT,
641 "debug": LOG_DEBUG,
642 "emerg": LOG_EMERG,
643 "err": LOG_ERR,
644 "error": LOG_ERR, # DEPRECATED
645 "info": LOG_INFO,
646 "notice": LOG_NOTICE,
647 "panic": LOG_EMERG, # DEPRECATED
648 "warn": LOG_WARNING, # DEPRECATED
649 "warning": LOG_WARNING,
650 }
651
652 facility_names = {
653 "auth": LOG_AUTH,
654 "authpriv": LOG_AUTHPRIV,
655 "cron": LOG_CRON,
656 "daemon": LOG_DAEMON,
657 "kern": LOG_KERN,
658 "lpr": LOG_LPR,
659 "mail": LOG_MAIL,
660 "news": LOG_NEWS,
661 "security": LOG_AUTH, # DEPRECATED
662 "syslog": LOG_SYSLOG,
663 "user": LOG_USER,
664 "uucp": LOG_UUCP,
665 "local0": LOG_LOCAL0,
666 "local1": LOG_LOCAL1,
667 "local2": LOG_LOCAL2,
668 "local3": LOG_LOCAL3,
669 "local4": LOG_LOCAL4,
670 "local5": LOG_LOCAL5,
671 "local6": LOG_LOCAL6,
672 "local7": LOG_LOCAL7,
673 }
674
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000675 #The map below appears to be trivially lowercasing the key. However,
676 #there's more to it than meets the eye - in some locales, lowercasing
677 #gives unexpected results. See SF #1524081: in the Turkish locale,
678 #"INFO".lower() != "info"
679 priority_map = {
680 "DEBUG" : "debug",
681 "INFO" : "info",
682 "WARNING" : "warning",
683 "ERROR" : "error",
684 "CRITICAL" : "critical"
685 }
686
Guido van Rossum57102f82002-11-13 16:15:58 +0000687 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER):
688 """
689 Initialize a handler.
690
Guido van Rossume7ba4952007-06-06 23:52:48 +0000691 If address is specified as a string, a UNIX socket is used. To log to a
692 local syslogd, "SysLogHandler(address="/dev/log")" can be used.
Guido van Rossum57102f82002-11-13 16:15:58 +0000693 If facility is not specified, LOG_USER is used.
694 """
695 logging.Handler.__init__(self)
696
697 self.address = address
698 self.facility = facility
Guido van Rossum13257902007-06-07 23:15:56 +0000699 if isinstance(address, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000700 self.unixsocket = 1
Thomas Wouters89f507f2006-12-13 04:49:30 +0000701 self._connect_unixsocket(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000702 else:
Guido van Rossum57102f82002-11-13 16:15:58 +0000703 self.unixsocket = 0
Thomas Wouters89f507f2006-12-13 04:49:30 +0000704 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Guido van Rossum57102f82002-11-13 16:15:58 +0000705
706 self.formatter = None
707
Vinay Sajipa1974c12005-01-13 08:23:56 +0000708 def _connect_unixsocket(self, address):
709 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
710 # syslog may require either DGRAM or STREAM sockets
711 try:
712 self.socket.connect(address)
713 except socket.error:
714 self.socket.close()
715 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Vinay Sajip8b6b53f2005-11-09 13:55:13 +0000716 self.socket.connect(address)
Vinay Sajipa1974c12005-01-13 08:23:56 +0000717
Guido van Rossum57102f82002-11-13 16:15:58 +0000718 # curious: when talking to the unix-domain '/dev/log' socket, a
719 # zero-terminator seems to be required. this string is placed
720 # into a class variable so that it can be overridden if
721 # necessary.
722 log_format_string = '<%d>%s\000'
723
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000724 def encodePriority(self, facility, priority):
Guido van Rossum57102f82002-11-13 16:15:58 +0000725 """
726 Encode the facility and priority. You can pass in strings or
727 integers - if strings are passed, the facility_names and
728 priority_names mapping dictionaries are used to convert them to
729 integers.
730 """
Guido van Rossum13257902007-06-07 23:15:56 +0000731 if isinstance(facility, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000732 facility = self.facility_names[facility]
Guido van Rossum13257902007-06-07 23:15:56 +0000733 if isinstance(priority, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000734 priority = self.priority_names[priority]
735 return (facility << 3) | priority
736
737 def close (self):
738 """
739 Closes the socket.
740 """
741 if self.unixsocket:
742 self.socket.close()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000743 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000744
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000745 def mapPriority(self, levelName):
746 """
747 Map a logging level name to a key in the priority_names map.
748 This is useful in two scenarios: when custom levels are being
749 used, and in the case where you can't do a straightforward
750 mapping by lowercasing the logging level name because of locale-
751 specific issues (see SF #1524081).
752 """
753 return self.priority_map.get(levelName, "warning")
754
Guido van Rossum57102f82002-11-13 16:15:58 +0000755 def emit(self, record):
756 """
757 Emit a record.
758
759 The record is formatted, and then sent to the syslog server. If
760 exception information is present, it is NOT sent to the server.
761 """
762 msg = self.format(record)
763 """
764 We need to convert record level to lowercase, maybe this will
765 change in the future.
766 """
767 msg = self.log_format_string % (
768 self.encodePriority(self.facility,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000769 self.mapPriority(record.levelname)),
770 msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000771 try:
772 if self.unixsocket:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000773 try:
774 self.socket.send(msg)
775 except socket.error:
776 self._connect_unixsocket(self.address)
777 self.socket.send(msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000778 else:
779 self.socket.sendto(msg, self.address)
Vinay Sajip85c19092005-10-31 13:14:19 +0000780 except (KeyboardInterrupt, SystemExit):
781 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000782 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000783 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000784
785class SMTPHandler(logging.Handler):
786 """
787 A handler class which sends an SMTP email for each logging event.
788 """
Guido van Rossum360e4b82007-05-14 22:51:27 +0000789 def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000790 """
791 Initialize the handler.
792
793 Initialize the instance with the from and to addresses and subject
794 line of the email. To specify a non-standard SMTP port, use the
Guido van Rossum360e4b82007-05-14 22:51:27 +0000795 (host, port) tuple format for the mailhost argument. To specify
796 authentication credentials, supply a (username, password) tuple
797 for the credentials argument.
Guido van Rossum57102f82002-11-13 16:15:58 +0000798 """
799 logging.Handler.__init__(self)
Guido van Rossum13257902007-06-07 23:15:56 +0000800 if isinstance(mailhost, tuple):
Guido van Rossum360e4b82007-05-14 22:51:27 +0000801 self.mailhost, self.mailport = mailhost
Guido van Rossum57102f82002-11-13 16:15:58 +0000802 else:
Guido van Rossum360e4b82007-05-14 22:51:27 +0000803 self.mailhost, self.mailport = mailhost, None
Guido van Rossum13257902007-06-07 23:15:56 +0000804 if isinstance(credentials, tuple):
Guido van Rossum360e4b82007-05-14 22:51:27 +0000805 self.username, self.password = credentials
806 else:
807 self.username = None
Guido van Rossum57102f82002-11-13 16:15:58 +0000808 self.fromaddr = fromaddr
Guido van Rossum13257902007-06-07 23:15:56 +0000809 if isinstance(toaddrs, str):
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000810 toaddrs = [toaddrs]
Guido van Rossum57102f82002-11-13 16:15:58 +0000811 self.toaddrs = toaddrs
812 self.subject = subject
813
814 def getSubject(self, record):
815 """
816 Determine the subject for the email.
817
818 If you want to specify a subject line which is record-dependent,
819 override this method.
820 """
821 return self.subject
822
Vinay Sajipe7d40662004-10-03 19:12:07 +0000823 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
824
825 monthname = [None,
826 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
827 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
828
829 def date_time(self):
830 """
831 Return the current date and time formatted for a MIME header.
832 Needed for Python 1.5.2 (no email package available)
833 """
834 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
835 s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
836 self.weekdayname[wd],
837 day, self.monthname[month], year,
838 hh, mm, ss)
839 return s
840
Guido van Rossum57102f82002-11-13 16:15:58 +0000841 def emit(self, record):
842 """
843 Emit a record.
844
845 Format the record and send it to the specified addressees.
846 """
847 try:
848 import smtplib
Vinay Sajipe7d40662004-10-03 19:12:07 +0000849 try:
Thomas Woutersb2137042007-02-01 18:02:27 +0000850 from email.utils import formatdate
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000851 except ImportError:
Vinay Sajipe7d40662004-10-03 19:12:07 +0000852 formatdate = self.date_time
Guido van Rossum57102f82002-11-13 16:15:58 +0000853 port = self.mailport
854 if not port:
855 port = smtplib.SMTP_PORT
856 smtp = smtplib.SMTP(self.mailhost, port)
857 msg = self.format(record)
Neal Norwitzf297bd12003-04-23 03:49:43 +0000858 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 +0000859 self.fromaddr,
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000860 ",".join(self.toaddrs),
Neal Norwitzf297bd12003-04-23 03:49:43 +0000861 self.getSubject(record),
Martin v. Löwis318a12e2004-08-18 12:27:40 +0000862 formatdate(), msg)
Guido van Rossum360e4b82007-05-14 22:51:27 +0000863 if self.username:
864 smtp.login(self.username, self.password)
Guido van Rossum57102f82002-11-13 16:15:58 +0000865 smtp.sendmail(self.fromaddr, self.toaddrs, msg)
866 smtp.quit()
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000867 except (KeyboardInterrupt, SystemExit):
868 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000869 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000870 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000871
872class NTEventLogHandler(logging.Handler):
873 """
874 A handler class which sends events to the NT Event Log. Adds a
875 registry entry for the specified application name. If no dllname is
876 provided, win32service.pyd (which contains some basic message
877 placeholders) is used. Note that use of these placeholders will make
878 your event logs big, as the entire message source is held in the log.
879 If you want slimmer logs, you have to pass in the name of your own DLL
880 which contains the message definitions you want to use in the event log.
881 """
882 def __init__(self, appname, dllname=None, logtype="Application"):
883 logging.Handler.__init__(self)
884 try:
885 import win32evtlogutil, win32evtlog
886 self.appname = appname
887 self._welu = win32evtlogutil
888 if not dllname:
889 dllname = os.path.split(self._welu.__file__)
890 dllname = os.path.split(dllname[0])
891 dllname = os.path.join(dllname[0], r'win32service.pyd')
892 self.dllname = dllname
893 self.logtype = logtype
894 self._welu.AddSourceToRegistry(appname, dllname, logtype)
895 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
896 self.typemap = {
897 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
898 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000899 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
Guido van Rossum57102f82002-11-13 16:15:58 +0000900 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
901 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
902 }
903 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000904 print("The Python Win32 extensions for NT (service, event "\
905 "logging) appear not to be available.")
Guido van Rossum57102f82002-11-13 16:15:58 +0000906 self._welu = None
907
908 def getMessageID(self, record):
909 """
910 Return the message ID for the event record. If you are using your
911 own messages, you could do this by having the msg passed to the
912 logger being an ID rather than a formatting string. Then, in here,
913 you could use a dictionary lookup to get the message ID. This
914 version returns 1, which is the base message ID in win32service.pyd.
915 """
916 return 1
917
918 def getEventCategory(self, record):
919 """
920 Return the event category for the record.
921
922 Override this if you want to specify your own categories. This version
923 returns 0.
924 """
925 return 0
926
927 def getEventType(self, record):
928 """
929 Return the event type for the record.
930
931 Override this if you want to specify your own types. This version does
932 a mapping using the handler's typemap attribute, which is set up in
933 __init__() to a dictionary which contains mappings for DEBUG, INFO,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000934 WARNING, ERROR and CRITICAL. If you are using your own levels you will
Guido van Rossum57102f82002-11-13 16:15:58 +0000935 either need to override this method or place a suitable dictionary in
936 the handler's typemap attribute.
937 """
938 return self.typemap.get(record.levelno, self.deftype)
939
940 def emit(self, record):
941 """
942 Emit a record.
943
944 Determine the message ID, event category and event type. Then
945 log the message in the NT event log.
946 """
947 if self._welu:
948 try:
949 id = self.getMessageID(record)
950 cat = self.getEventCategory(record)
951 type = self.getEventType(record)
952 msg = self.format(record)
953 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000954 except (KeyboardInterrupt, SystemExit):
955 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000956 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000957 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000958
959 def close(self):
960 """
961 Clean up this handler.
962
963 You can remove the application name from the registry as a
964 source of event log entries. However, if you do this, you will
965 not be able to see the events as you intended in the Event Log
966 Viewer - it needs to be able to access the registry to get the
967 DLL name.
968 """
969 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000970 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000971
972class HTTPHandler(logging.Handler):
973 """
974 A class which sends records to a Web server, using either GET or
975 POST semantics.
976 """
977 def __init__(self, host, url, method="GET"):
978 """
979 Initialize the instance with the host, the request URL, and the method
980 ("GET" or "POST")
981 """
982 logging.Handler.__init__(self)
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000983 method = method.upper()
Guido van Rossum57102f82002-11-13 16:15:58 +0000984 if method not in ["GET", "POST"]:
Collin Winterce36ad82007-08-30 01:19:48 +0000985 raise ValueError("method must be GET or POST")
Guido van Rossum57102f82002-11-13 16:15:58 +0000986 self.host = host
987 self.url = url
988 self.method = method
989
Neal Norwitzf297bd12003-04-23 03:49:43 +0000990 def mapLogRecord(self, record):
991 """
992 Default implementation of mapping the log record into a dict
Vinay Sajip48cfe382004-02-20 13:17:27 +0000993 that is sent as the CGI data. Overwrite in your class.
Neal Norwitzf297bd12003-04-23 03:49:43 +0000994 Contributed by Franz Glasner.
995 """
996 return record.__dict__
997
Guido van Rossum57102f82002-11-13 16:15:58 +0000998 def emit(self, record):
999 """
1000 Emit a record.
1001
1002 Send the record to the Web server as an URL-encoded dictionary
1003 """
1004 try:
Georg Brandl24420152008-05-26 16:32:26 +00001005 import http.client, urllib
Vinay Sajipb7935062005-10-11 13:15:31 +00001006 host = self.host
Georg Brandl24420152008-05-26 16:32:26 +00001007 h = http.client.HTTP(host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001008 url = self.url
Neal Norwitzf297bd12003-04-23 03:49:43 +00001009 data = urllib.urlencode(self.mapLogRecord(record))
Guido van Rossum57102f82002-11-13 16:15:58 +00001010 if self.method == "GET":
Neal Norwitz9d72bb42007-04-17 08:48:32 +00001011 if (url.find('?') >= 0):
Guido van Rossum57102f82002-11-13 16:15:58 +00001012 sep = '&'
1013 else:
1014 sep = '?'
1015 url = url + "%c%s" % (sep, data)
1016 h.putrequest(self.method, url)
Vinay Sajipb7935062005-10-11 13:15:31 +00001017 # support multiple hosts on one IP address...
1018 # need to strip optional :port from host, if present
Neal Norwitz9d72bb42007-04-17 08:48:32 +00001019 i = host.find(":")
Vinay Sajipb7935062005-10-11 13:15:31 +00001020 if i >= 0:
1021 host = host[:i]
1022 h.putheader("Host", host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001023 if self.method == "POST":
Vinay Sajipb7935062005-10-11 13:15:31 +00001024 h.putheader("Content-type",
1025 "application/x-www-form-urlencoded")
Guido van Rossum57102f82002-11-13 16:15:58 +00001026 h.putheader("Content-length", str(len(data)))
1027 h.endheaders()
1028 if self.method == "POST":
1029 h.send(data)
1030 h.getreply() #can't do anything with the result
Vinay Sajip245a5ab2005-10-31 14:27:01 +00001031 except (KeyboardInterrupt, SystemExit):
1032 raise
Guido van Rossum57102f82002-11-13 16:15:58 +00001033 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +00001034 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +00001035
1036class BufferingHandler(logging.Handler):
1037 """
1038 A handler class which buffers logging records in memory. Whenever each
1039 record is added to the buffer, a check is made to see if the buffer should
1040 be flushed. If it should, then flush() is expected to do what's needed.
1041 """
1042 def __init__(self, capacity):
1043 """
1044 Initialize the handler with the buffer size.
1045 """
1046 logging.Handler.__init__(self)
1047 self.capacity = capacity
1048 self.buffer = []
1049
1050 def shouldFlush(self, record):
1051 """
1052 Should the handler flush its buffer?
1053
1054 Returns true if the buffer is up to capacity. This method can be
1055 overridden to implement custom flushing strategies.
1056 """
1057 return (len(self.buffer) >= self.capacity)
1058
1059 def emit(self, record):
1060 """
1061 Emit a record.
1062
1063 Append the record. If shouldFlush() tells us to, call flush() to process
1064 the buffer.
1065 """
1066 self.buffer.append(record)
1067 if self.shouldFlush(record):
1068 self.flush()
1069
1070 def flush(self):
1071 """
1072 Override to implement custom flushing behaviour.
1073
1074 This version just zaps the buffer to empty.
1075 """
1076 self.buffer = []
1077
Vinay Sajipf42d95e2004-02-21 22:14:34 +00001078 def close(self):
1079 """
1080 Close the handler.
1081
1082 This version just flushes and chains to the parent class' close().
1083 """
1084 self.flush()
1085 logging.Handler.close(self)
1086
Guido van Rossum57102f82002-11-13 16:15:58 +00001087class MemoryHandler(BufferingHandler):
1088 """
1089 A handler class which buffers logging records in memory, periodically
1090 flushing them to a target handler. Flushing occurs whenever the buffer
1091 is full, or when an event of a certain severity or greater is seen.
1092 """
1093 def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
1094 """
1095 Initialize the handler with the buffer size, the level at which
1096 flushing should occur and an optional target.
1097
1098 Note that without a target being set either here or via setTarget(),
1099 a MemoryHandler is no use to anyone!
1100 """
1101 BufferingHandler.__init__(self, capacity)
1102 self.flushLevel = flushLevel
1103 self.target = target
1104
1105 def shouldFlush(self, record):
1106 """
1107 Check for buffer full or a record at the flushLevel or higher.
1108 """
1109 return (len(self.buffer) >= self.capacity) or \
1110 (record.levelno >= self.flushLevel)
1111
1112 def setTarget(self, target):
1113 """
1114 Set the target handler for this handler.
1115 """
1116 self.target = target
1117
1118 def flush(self):
1119 """
1120 For a MemoryHandler, flushing means just sending the buffered
1121 records to the target, if there is one. Override if you want
1122 different behaviour.
1123 """
1124 if self.target:
1125 for record in self.buffer:
1126 self.target.handle(record)
1127 self.buffer = []
1128
1129 def close(self):
1130 """
1131 Flush, set the target to None and lose the buffer.
1132 """
1133 self.flush()
1134 self.target = None
Vinay Sajip48cfe382004-02-20 13:17:27 +00001135 BufferingHandler.close(self)