blob: d9acb87254ee8a83be5f4e0debe7a54665fdbdd4 [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
Vinay Sajip6268cbc2009-01-21 00:19:28 +000022Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000023
Vinay Sajip6268cbc2009-01-21 00:19:28 +000024To use, simply 'import logging.handlers' and log away!
Guido van Rossum57102f82002-11-13 16:15:58 +000025"""
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 """
Vinay Sajip6268cbc2009-01-21 00:19:28 +0000115 if self.stream:
116 self.stream.close()
Guido van Rossum57102f82002-11-13 16:15:58 +0000117 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 """
Vinay Sajip6268cbc2009-01-21 00:19:28 +0000141 if self.stream is None: # delay was set...
142 self.stream = self._open()
Guido van Rossum57102f82002-11-13 16:15:58 +0000143 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 """
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000158 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0):
Christian Heimese7a15bb2008-01-24 16:21:45 +0000159 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000160 self.when = when.upper()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000161 self.backupCount = backupCount
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000162 self.utc = utc
Vinay Sajip17c52d82004-07-03 11:48:34 +0000163 # Calculate the real rollover interval, which is just the number of
164 # seconds between rollovers. Also set the filename suffix used when
165 # a rollover occurs. Current 'when' events supported:
166 # S - Seconds
167 # M - Minutes
168 # H - Hours
169 # D - Days
170 # midnight - roll over at midnight
171 # W{0-6} - roll over on a certain day; 0 - Monday
172 #
173 # Case of the 'when' specifier is not important; lower or upper case
174 # will work.
175 currentTime = int(time.time())
176 if self.when == 'S':
177 self.interval = 1 # one second
178 self.suffix = "%Y-%m-%d_%H-%M-%S"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000179 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000180 elif self.when == 'M':
181 self.interval = 60 # one minute
182 self.suffix = "%Y-%m-%d_%H-%M"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000183 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000184 elif self.when == 'H':
185 self.interval = 60 * 60 # one hour
186 self.suffix = "%Y-%m-%d_%H"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000187 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000188 elif self.when == 'D' or self.when == 'MIDNIGHT':
189 self.interval = 60 * 60 * 24 # one day
190 self.suffix = "%Y-%m-%d"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000191 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000192 elif self.when.startswith('W'):
193 self.interval = 60 * 60 * 24 * 7 # one week
194 if len(self.when) != 2:
195 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
196 if self.when[1] < '0' or self.when[1] > '6':
197 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
198 self.dayOfWeek = int(self.when[1])
199 self.suffix = "%Y-%m-%d"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000200 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000201 else:
202 raise ValueError("Invalid rollover interval specified: %s" % self.when)
203
Antoine Pitroufd036452008-08-19 17:56:33 +0000204 self.extMatch = re.compile(self.extMatch, re.ASCII)
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
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000217 if utc:
218 t = time.gmtime(currentTime)
219 else:
220 t = time.localtime(currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000221 currentHour = t[3]
222 currentMinute = t[4]
223 currentSecond = t[5]
224 # r is the number of seconds left between now and midnight
Thomas Wouters477c8d52006-05-27 19:21:47 +0000225 r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
226 currentSecond)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000227 self.rolloverAt = currentTime + r
228 # If we are rolling over on a certain day, add in the number of days until
229 # the next rollover, but offset by 1 since we just calculated the time
230 # until the next day starts. There are three cases:
231 # Case 1) The day to rollover is today; in this case, do nothing
232 # Case 2) The day to rollover is further in the interval (i.e., today is
233 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
234 # next rollover is simply 6 - 2 - 1, or 3.
235 # Case 3) The day to rollover is behind us in the interval (i.e., today
236 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
237 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
238 # number of days left in the current week (1) plus the number
239 # of days in the next week until the rollover day (3).
Georg Brandl86def6c2008-01-21 20:36:10 +0000240 # The calculations described in 2) and 3) above need to have a day added.
241 # This is because the above time calculation takes us to midnight on this
242 # day, i.e. the start of the next day.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000243 if when.startswith('W'):
244 day = t[6] # 0 is Monday
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000245 if day != self.dayOfWeek:
246 if day < self.dayOfWeek:
Georg Brandl86def6c2008-01-21 20:36:10 +0000247 daysToWait = self.dayOfWeek - day
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000248 else:
Georg Brandl86def6c2008-01-21 20:36:10 +0000249 daysToWait = 6 - day + self.dayOfWeek + 1
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000250 newRolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24))
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000251 if not utc:
252 dstNow = t[-1]
253 dstAtRollover = time.localtime(newRolloverAt)[-1]
254 if dstNow != dstAtRollover:
255 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
256 newRolloverAt = newRolloverAt - 3600
257 else: # DST bows out before next rollover, so we need to add an hour
258 newRolloverAt = newRolloverAt + 3600
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000259 self.rolloverAt = newRolloverAt
Vinay Sajip17c52d82004-07-03 11:48:34 +0000260
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000261 #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000262
263 def shouldRollover(self, record):
264 """
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000265 Determine if rollover should occur.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000266
267 record is not used, as we are just comparing times, but it is needed so
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000268 the method signatures are the same
Vinay Sajip17c52d82004-07-03 11:48:34 +0000269 """
270 t = int(time.time())
271 if t >= self.rolloverAt:
272 return 1
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000273 #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000274 return 0
275
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000276 def getFilesToDelete(self):
277 """
278 Determine the files to delete when rolling over.
279
280 More specific than the earlier method, which just used glob.glob().
281 """
282 dirName, baseName = os.path.split(self.baseFilename)
283 fileNames = os.listdir(dirName)
284 result = []
285 prefix = baseName + "."
286 plen = len(prefix)
287 for fileName in fileNames:
288 if fileName[:plen] == prefix:
289 suffix = fileName[plen:]
290 if self.extMatch.match(suffix):
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000291 result.append(os.path.join(dirName, fileName))
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000292 result.sort()
293 if len(result) < self.backupCount:
294 result = []
295 else:
296 result = result[:len(result) - self.backupCount]
297 return result
298
Vinay Sajip17c52d82004-07-03 11:48:34 +0000299 def doRollover(self):
300 """
301 do a rollover; in this case, a date/time stamp is appended to the filename
302 when the rollover happens. However, you want the file to be named for the
303 start of the interval, not the current time. If there is a backup count,
304 then we have to get a list of matching filenames, sort them and remove
305 the one with the oldest suffix.
306 """
Vinay Sajip6268cbc2009-01-21 00:19:28 +0000307 if self.stream:
308 self.stream.close()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000309 # get the time that this sequence started at and make it a TimeTuple
310 t = self.rolloverAt - self.interval
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000311 if self.utc:
312 timeTuple = time.gmtime(t)
313 else:
314 timeTuple = time.localtime(t)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000315 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
316 if os.path.exists(dfn):
317 os.remove(dfn)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000318 os.rename(self.baseFilename, dfn)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000319 if self.backupCount > 0:
320 # find the oldest log file and delete it
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000321 #s = glob.glob(self.baseFilename + ".20*")
322 #if len(s) > self.backupCount:
323 # s.sort()
324 # os.remove(s[0])
325 for s in self.getFilesToDelete():
326 os.remove(s)
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000327 #print "%s -> %s" % (self.baseFilename, dfn)
Thomas Woutersb2137042007-02-01 18:02:27 +0000328 self.mode = 'w'
329 self.stream = self._open()
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000330 newRolloverAt = self.rolloverAt + self.interval
331 currentTime = int(time.time())
332 while newRolloverAt <= currentTime:
333 newRolloverAt = newRolloverAt + self.interval
334 #If DST changes and midnight or weekly rollover, adjust for this.
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000335 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000336 dstNow = time.localtime(currentTime)[-1]
337 dstAtRollover = time.localtime(newRolloverAt)[-1]
338 if dstNow != dstAtRollover:
339 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
340 newRolloverAt = newRolloverAt - 3600
341 else: # DST bows out before next rollover, so we need to add an hour
342 newRolloverAt = newRolloverAt + 3600
343 self.rolloverAt = newRolloverAt
Guido van Rossum57102f82002-11-13 16:15:58 +0000344
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000345class WatchedFileHandler(logging.FileHandler):
346 """
347 A handler for logging to a file, which watches the file
348 to see if it has changed while in use. This can happen because of
349 usage of programs such as newsyslog and logrotate which perform
350 log file rotation. This handler, intended for use under Unix,
351 watches the file to see if it has changed since the last emit.
352 (A file has changed if its device or inode have changed.)
353 If it has changed, the old file stream is closed, and the file
354 opened to get a new stream.
355
356 This handler is not appropriate for use under Windows, because
357 under Windows open files cannot be moved or renamed - logging
358 opens the files with exclusive locks - and so there is no need
359 for such a handler. Furthermore, ST_INO is not supported under
360 Windows; stat always returns zero for this value.
361
362 This handler is based on a suggestion and patch by Chad J.
363 Schroeder.
364 """
Christian Heimese7a15bb2008-01-24 16:21:45 +0000365 def __init__(self, filename, mode='a', encoding=None, delay=0):
366 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
367 if not os.path.exists(self.baseFilename):
368 self.dev, self.ino = -1, -1
369 else:
370 stat = os.stat(self.baseFilename)
371 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000372
373 def emit(self, record):
374 """
375 Emit a record.
376
377 First check if the underlying file has changed, and if it
378 has, close the old stream and reopen the file to get the
379 current stream.
380 """
381 if not os.path.exists(self.baseFilename):
382 stat = None
383 changed = 1
384 else:
385 stat = os.stat(self.baseFilename)
386 changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
Christian Heimese7a15bb2008-01-24 16:21:45 +0000387 if changed and self.stream is not None:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000388 self.stream.flush()
389 self.stream.close()
390 self.stream = self._open()
391 if stat is None:
392 stat = os.stat(self.baseFilename)
393 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
394 logging.FileHandler.emit(self, record)
395
Guido van Rossum57102f82002-11-13 16:15:58 +0000396class SocketHandler(logging.Handler):
397 """
398 A handler class which writes logging records, in pickle format, to
399 a streaming socket. The socket is kept open across logging calls.
400 If the peer resets it, an attempt is made to reconnect on the next call.
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000401 The pickle which is sent is that of the LogRecord's attribute dictionary
402 (__dict__), so that the receiver does not need to have the logging module
403 installed in order to process the logging event.
404
405 To unpickle the record at the receiving end into a LogRecord, use the
406 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000407 """
408
409 def __init__(self, host, port):
410 """
411 Initializes the handler with a specific host address and port.
412
413 The attribute 'closeOnError' is set to 1 - which means that if
414 a socket error occurs, the socket is silently closed and then
415 reopened on the next logging call.
416 """
417 logging.Handler.__init__(self)
418 self.host = host
419 self.port = port
420 self.sock = None
421 self.closeOnError = 0
Vinay Sajip48cfe382004-02-20 13:17:27 +0000422 self.retryTime = None
423 #
424 # Exponential backoff parameters.
425 #
426 self.retryStart = 1.0
427 self.retryMax = 30.0
428 self.retryFactor = 2.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000429
Guido van Rossumd8faa362007-04-27 19:54:29 +0000430 def makeSocket(self, timeout=1):
Guido van Rossum57102f82002-11-13 16:15:58 +0000431 """
432 A factory method which allows subclasses to define the precise
433 type of socket they want.
434 """
435 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000436 if hasattr(s, 'settimeout'):
437 s.settimeout(timeout)
Guido van Rossum57102f82002-11-13 16:15:58 +0000438 s.connect((self.host, self.port))
439 return s
440
Vinay Sajip48cfe382004-02-20 13:17:27 +0000441 def createSocket(self):
442 """
443 Try to create a socket, using an exponential backoff with
444 a max retry time. Thanks to Robert Olson for the original patch
445 (SF #815911) which has been slightly refactored.
446 """
447 now = time.time()
448 # Either retryTime is None, in which case this
449 # is the first time back after a disconnect, or
450 # we've waited long enough.
451 if self.retryTime is None:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000452 attempt = 1
Vinay Sajip48cfe382004-02-20 13:17:27 +0000453 else:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000454 attempt = (now >= self.retryTime)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000455 if attempt:
456 try:
457 self.sock = self.makeSocket()
458 self.retryTime = None # next time, no delay before trying
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000459 except socket.error:
Vinay Sajip48cfe382004-02-20 13:17:27 +0000460 #Creation failed, so set the retry time and return.
461 if self.retryTime is None:
462 self.retryPeriod = self.retryStart
463 else:
464 self.retryPeriod = self.retryPeriod * self.retryFactor
465 if self.retryPeriod > self.retryMax:
466 self.retryPeriod = self.retryMax
467 self.retryTime = now + self.retryPeriod
468
Guido van Rossum57102f82002-11-13 16:15:58 +0000469 def send(self, s):
470 """
471 Send a pickled string to the socket.
472
473 This function allows for partial sends which can happen when the
474 network is busy.
475 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000476 if self.sock is None:
477 self.createSocket()
478 #self.sock can be None either because we haven't reached the retry
479 #time yet, or because we have reached the retry time and retried,
480 #but are still unable to connect.
481 if self.sock:
482 try:
483 if hasattr(self.sock, "sendall"):
484 self.sock.sendall(s)
485 else:
486 sentsofar = 0
487 left = len(s)
488 while left > 0:
489 sent = self.sock.send(s[sentsofar:])
490 sentsofar = sentsofar + sent
491 left = left - sent
492 except socket.error:
493 self.sock.close()
494 self.sock = None # so we can call createSocket next time
Guido van Rossum57102f82002-11-13 16:15:58 +0000495
496 def makePickle(self, record):
497 """
498 Pickles the record in binary format with a length prefix, and
499 returns it ready for transmission across the socket.
500 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000501 ei = record.exc_info
502 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000503 dummy = self.format(record) # just to get traceback text into record.exc_text
504 record.exc_info = None # to avoid Unpickleable error
Guido van Rossumba205d62006-08-17 08:57:26 +0000505 s = pickle.dumps(record.__dict__, 1)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000506 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000507 record.exc_info = ei # for next handler
Guido van Rossum57102f82002-11-13 16:15:58 +0000508 slen = struct.pack(">L", len(s))
509 return slen + s
510
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000511 def handleError(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000512 """
513 Handle an error during logging.
514
515 An error has occurred during logging. Most likely cause -
516 connection lost. Close the socket so that we can retry on the
517 next event.
518 """
519 if self.closeOnError and self.sock:
520 self.sock.close()
521 self.sock = None #try to reconnect next time
522 else:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000523 logging.Handler.handleError(self, record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000524
525 def emit(self, record):
526 """
527 Emit a record.
528
529 Pickles the record and writes it to the socket in binary format.
530 If there is an error with the socket, silently drop the packet.
531 If there was a problem with the socket, re-establishes the
532 socket.
533 """
534 try:
535 s = self.makePickle(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000536 self.send(s)
Vinay Sajip85c19092005-10-31 13:14:19 +0000537 except (KeyboardInterrupt, SystemExit):
538 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000539 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000540 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000541
542 def close(self):
543 """
544 Closes the socket.
545 """
546 if self.sock:
547 self.sock.close()
548 self.sock = None
Vinay Sajip48cfe382004-02-20 13:17:27 +0000549 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000550
551class DatagramHandler(SocketHandler):
552 """
553 A handler class which writes logging records, in pickle format, to
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000554 a datagram socket. The pickle which is sent is that of the LogRecord's
555 attribute dictionary (__dict__), so that the receiver does not need to
556 have the logging module installed in order to process the logging event.
557
558 To unpickle the record at the receiving end into a LogRecord, use the
559 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000560
561 """
562 def __init__(self, host, port):
563 """
564 Initializes the handler with a specific host address and port.
565 """
566 SocketHandler.__init__(self, host, port)
567 self.closeOnError = 0
568
569 def makeSocket(self):
570 """
571 The factory method of SocketHandler is here overridden to create
572 a UDP socket (SOCK_DGRAM).
573 """
574 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
575 return s
576
577 def send(self, s):
578 """
579 Send a pickled string to a socket.
580
581 This function no longer allows for partial sends which can happen
582 when the network is busy - UDP does not guarantee delivery and
583 can deliver packets out of sequence.
584 """
Vinay Sajipfb154172004-08-24 09:36:23 +0000585 if self.sock is None:
586 self.createSocket()
Guido van Rossum57102f82002-11-13 16:15:58 +0000587 self.sock.sendto(s, (self.host, self.port))
588
589class SysLogHandler(logging.Handler):
590 """
591 A handler class which sends formatted logging records to a syslog
592 server. Based on Sam Rushing's syslog module:
593 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
594 Contributed by Nicolas Untz (after which minor refactoring changes
595 have been made).
596 """
597
598 # from <linux/sys/syslog.h>:
599 # ======================================================================
600 # priorities/facilities are encoded into a single 32-bit quantity, where
601 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
602 # facility (0-big number). Both the priorities and the facilities map
603 # roughly one-to-one to strings in the syslogd(8) source code. This
604 # mapping is included in this file.
605 #
606 # priorities (these are ordered)
607
608 LOG_EMERG = 0 # system is unusable
609 LOG_ALERT = 1 # action must be taken immediately
610 LOG_CRIT = 2 # critical conditions
611 LOG_ERR = 3 # error conditions
612 LOG_WARNING = 4 # warning conditions
613 LOG_NOTICE = 5 # normal but significant condition
614 LOG_INFO = 6 # informational
615 LOG_DEBUG = 7 # debug-level messages
616
617 # facility codes
618 LOG_KERN = 0 # kernel messages
619 LOG_USER = 1 # random user-level messages
620 LOG_MAIL = 2 # mail system
621 LOG_DAEMON = 3 # system daemons
622 LOG_AUTH = 4 # security/authorization messages
623 LOG_SYSLOG = 5 # messages generated internally by syslogd
624 LOG_LPR = 6 # line printer subsystem
625 LOG_NEWS = 7 # network news subsystem
626 LOG_UUCP = 8 # UUCP subsystem
627 LOG_CRON = 9 # clock daemon
628 LOG_AUTHPRIV = 10 # security/authorization messages (private)
629
630 # other codes through 15 reserved for system use
631 LOG_LOCAL0 = 16 # reserved for local use
632 LOG_LOCAL1 = 17 # reserved for local use
633 LOG_LOCAL2 = 18 # reserved for local use
634 LOG_LOCAL3 = 19 # reserved for local use
635 LOG_LOCAL4 = 20 # reserved for local use
636 LOG_LOCAL5 = 21 # reserved for local use
637 LOG_LOCAL6 = 22 # reserved for local use
638 LOG_LOCAL7 = 23 # reserved for local use
639
640 priority_names = {
641 "alert": LOG_ALERT,
642 "crit": LOG_CRIT,
643 "critical": LOG_CRIT,
644 "debug": LOG_DEBUG,
645 "emerg": LOG_EMERG,
646 "err": LOG_ERR,
647 "error": LOG_ERR, # DEPRECATED
648 "info": LOG_INFO,
649 "notice": LOG_NOTICE,
650 "panic": LOG_EMERG, # DEPRECATED
651 "warn": LOG_WARNING, # DEPRECATED
652 "warning": LOG_WARNING,
653 }
654
655 facility_names = {
656 "auth": LOG_AUTH,
657 "authpriv": LOG_AUTHPRIV,
658 "cron": LOG_CRON,
659 "daemon": LOG_DAEMON,
660 "kern": LOG_KERN,
661 "lpr": LOG_LPR,
662 "mail": LOG_MAIL,
663 "news": LOG_NEWS,
664 "security": LOG_AUTH, # DEPRECATED
665 "syslog": LOG_SYSLOG,
666 "user": LOG_USER,
667 "uucp": LOG_UUCP,
668 "local0": LOG_LOCAL0,
669 "local1": LOG_LOCAL1,
670 "local2": LOG_LOCAL2,
671 "local3": LOG_LOCAL3,
672 "local4": LOG_LOCAL4,
673 "local5": LOG_LOCAL5,
674 "local6": LOG_LOCAL6,
675 "local7": LOG_LOCAL7,
676 }
677
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000678 #The map below appears to be trivially lowercasing the key. However,
679 #there's more to it than meets the eye - in some locales, lowercasing
680 #gives unexpected results. See SF #1524081: in the Turkish locale,
681 #"INFO".lower() != "info"
682 priority_map = {
683 "DEBUG" : "debug",
684 "INFO" : "info",
685 "WARNING" : "warning",
686 "ERROR" : "error",
687 "CRITICAL" : "critical"
688 }
689
Guido van Rossum57102f82002-11-13 16:15:58 +0000690 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER):
691 """
692 Initialize a handler.
693
Guido van Rossume7ba4952007-06-06 23:52:48 +0000694 If address is specified as a string, a UNIX socket is used. To log to a
695 local syslogd, "SysLogHandler(address="/dev/log")" can be used.
Guido van Rossum57102f82002-11-13 16:15:58 +0000696 If facility is not specified, LOG_USER is used.
697 """
698 logging.Handler.__init__(self)
699
700 self.address = address
701 self.facility = facility
Guido van Rossum13257902007-06-07 23:15:56 +0000702 if isinstance(address, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000703 self.unixsocket = 1
Thomas Wouters89f507f2006-12-13 04:49:30 +0000704 self._connect_unixsocket(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000705 else:
Guido van Rossum57102f82002-11-13 16:15:58 +0000706 self.unixsocket = 0
Thomas Wouters89f507f2006-12-13 04:49:30 +0000707 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Guido van Rossum57102f82002-11-13 16:15:58 +0000708
709 self.formatter = None
710
Vinay Sajipa1974c12005-01-13 08:23:56 +0000711 def _connect_unixsocket(self, address):
712 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
713 # syslog may require either DGRAM or STREAM sockets
714 try:
715 self.socket.connect(address)
716 except socket.error:
717 self.socket.close()
718 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Vinay Sajip8b6b53f2005-11-09 13:55:13 +0000719 self.socket.connect(address)
Vinay Sajipa1974c12005-01-13 08:23:56 +0000720
Guido van Rossum57102f82002-11-13 16:15:58 +0000721 # curious: when talking to the unix-domain '/dev/log' socket, a
722 # zero-terminator seems to be required. this string is placed
723 # into a class variable so that it can be overridden if
724 # necessary.
725 log_format_string = '<%d>%s\000'
726
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000727 def encodePriority(self, facility, priority):
Guido van Rossum57102f82002-11-13 16:15:58 +0000728 """
729 Encode the facility and priority. You can pass in strings or
730 integers - if strings are passed, the facility_names and
731 priority_names mapping dictionaries are used to convert them to
732 integers.
733 """
Guido van Rossum13257902007-06-07 23:15:56 +0000734 if isinstance(facility, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000735 facility = self.facility_names[facility]
Guido van Rossum13257902007-06-07 23:15:56 +0000736 if isinstance(priority, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000737 priority = self.priority_names[priority]
738 return (facility << 3) | priority
739
740 def close (self):
741 """
742 Closes the socket.
743 """
744 if self.unixsocket:
745 self.socket.close()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000746 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000747
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000748 def mapPriority(self, levelName):
749 """
750 Map a logging level name to a key in the priority_names map.
751 This is useful in two scenarios: when custom levels are being
752 used, and in the case where you can't do a straightforward
753 mapping by lowercasing the logging level name because of locale-
754 specific issues (see SF #1524081).
755 """
756 return self.priority_map.get(levelName, "warning")
757
Guido van Rossum57102f82002-11-13 16:15:58 +0000758 def emit(self, record):
759 """
760 Emit a record.
761
762 The record is formatted, and then sent to the syslog server. If
763 exception information is present, it is NOT sent to the server.
764 """
765 msg = self.format(record)
766 """
767 We need to convert record level to lowercase, maybe this will
768 change in the future.
769 """
770 msg = self.log_format_string % (
771 self.encodePriority(self.facility,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000772 self.mapPriority(record.levelname)),
773 msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000774 try:
775 if self.unixsocket:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000776 try:
777 self.socket.send(msg)
778 except socket.error:
779 self._connect_unixsocket(self.address)
780 self.socket.send(msg)
Guido van Rossum57102f82002-11-13 16:15:58 +0000781 else:
782 self.socket.sendto(msg, self.address)
Vinay Sajip85c19092005-10-31 13:14:19 +0000783 except (KeyboardInterrupt, SystemExit):
784 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000785 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000786 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000787
788class SMTPHandler(logging.Handler):
789 """
790 A handler class which sends an SMTP email for each logging event.
791 """
Guido van Rossum360e4b82007-05-14 22:51:27 +0000792 def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000793 """
794 Initialize the handler.
795
796 Initialize the instance with the from and to addresses and subject
797 line of the email. To specify a non-standard SMTP port, use the
Guido van Rossum360e4b82007-05-14 22:51:27 +0000798 (host, port) tuple format for the mailhost argument. To specify
799 authentication credentials, supply a (username, password) tuple
800 for the credentials argument.
Guido van Rossum57102f82002-11-13 16:15:58 +0000801 """
802 logging.Handler.__init__(self)
Guido van Rossum13257902007-06-07 23:15:56 +0000803 if isinstance(mailhost, tuple):
Guido van Rossum360e4b82007-05-14 22:51:27 +0000804 self.mailhost, self.mailport = mailhost
Guido van Rossum57102f82002-11-13 16:15:58 +0000805 else:
Guido van Rossum360e4b82007-05-14 22:51:27 +0000806 self.mailhost, self.mailport = mailhost, None
Guido van Rossum13257902007-06-07 23:15:56 +0000807 if isinstance(credentials, tuple):
Guido van Rossum360e4b82007-05-14 22:51:27 +0000808 self.username, self.password = credentials
809 else:
810 self.username = None
Guido van Rossum57102f82002-11-13 16:15:58 +0000811 self.fromaddr = fromaddr
Guido van Rossum13257902007-06-07 23:15:56 +0000812 if isinstance(toaddrs, str):
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000813 toaddrs = [toaddrs]
Guido van Rossum57102f82002-11-13 16:15:58 +0000814 self.toaddrs = toaddrs
815 self.subject = subject
816
817 def getSubject(self, record):
818 """
819 Determine the subject for the email.
820
821 If you want to specify a subject line which is record-dependent,
822 override this method.
823 """
824 return self.subject
825
Vinay Sajipe7d40662004-10-03 19:12:07 +0000826 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
827
828 monthname = [None,
829 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
830 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
831
832 def date_time(self):
833 """
834 Return the current date and time formatted for a MIME header.
835 Needed for Python 1.5.2 (no email package available)
836 """
837 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
838 s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
839 self.weekdayname[wd],
840 day, self.monthname[month], year,
841 hh, mm, ss)
842 return s
843
Guido van Rossum57102f82002-11-13 16:15:58 +0000844 def emit(self, record):
845 """
846 Emit a record.
847
848 Format the record and send it to the specified addressees.
849 """
850 try:
851 import smtplib
Vinay Sajipe7d40662004-10-03 19:12:07 +0000852 try:
Thomas Woutersb2137042007-02-01 18:02:27 +0000853 from email.utils import formatdate
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000854 except ImportError:
Vinay Sajipe7d40662004-10-03 19:12:07 +0000855 formatdate = self.date_time
Guido van Rossum57102f82002-11-13 16:15:58 +0000856 port = self.mailport
857 if not port:
858 port = smtplib.SMTP_PORT
859 smtp = smtplib.SMTP(self.mailhost, port)
860 msg = self.format(record)
Neal Norwitzf297bd12003-04-23 03:49:43 +0000861 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 +0000862 self.fromaddr,
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000863 ",".join(self.toaddrs),
Neal Norwitzf297bd12003-04-23 03:49:43 +0000864 self.getSubject(record),
Martin v. Löwis318a12e2004-08-18 12:27:40 +0000865 formatdate(), msg)
Guido van Rossum360e4b82007-05-14 22:51:27 +0000866 if self.username:
867 smtp.login(self.username, self.password)
Guido van Rossum57102f82002-11-13 16:15:58 +0000868 smtp.sendmail(self.fromaddr, self.toaddrs, msg)
869 smtp.quit()
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000870 except (KeyboardInterrupt, SystemExit):
871 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000872 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000873 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000874
875class NTEventLogHandler(logging.Handler):
876 """
877 A handler class which sends events to the NT Event Log. Adds a
878 registry entry for the specified application name. If no dllname is
879 provided, win32service.pyd (which contains some basic message
880 placeholders) is used. Note that use of these placeholders will make
881 your event logs big, as the entire message source is held in the log.
882 If you want slimmer logs, you have to pass in the name of your own DLL
883 which contains the message definitions you want to use in the event log.
884 """
885 def __init__(self, appname, dllname=None, logtype="Application"):
886 logging.Handler.__init__(self)
887 try:
888 import win32evtlogutil, win32evtlog
889 self.appname = appname
890 self._welu = win32evtlogutil
891 if not dllname:
892 dllname = os.path.split(self._welu.__file__)
893 dllname = os.path.split(dllname[0])
894 dllname = os.path.join(dllname[0], r'win32service.pyd')
895 self.dllname = dllname
896 self.logtype = logtype
897 self._welu.AddSourceToRegistry(appname, dllname, logtype)
898 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
899 self.typemap = {
900 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
901 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000902 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
Guido van Rossum57102f82002-11-13 16:15:58 +0000903 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
904 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
905 }
906 except ImportError:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000907 print("The Python Win32 extensions for NT (service, event "\
908 "logging) appear not to be available.")
Guido van Rossum57102f82002-11-13 16:15:58 +0000909 self._welu = None
910
911 def getMessageID(self, record):
912 """
913 Return the message ID for the event record. If you are using your
914 own messages, you could do this by having the msg passed to the
915 logger being an ID rather than a formatting string. Then, in here,
916 you could use a dictionary lookup to get the message ID. This
917 version returns 1, which is the base message ID in win32service.pyd.
918 """
919 return 1
920
921 def getEventCategory(self, record):
922 """
923 Return the event category for the record.
924
925 Override this if you want to specify your own categories. This version
926 returns 0.
927 """
928 return 0
929
930 def getEventType(self, record):
931 """
932 Return the event type for the record.
933
934 Override this if you want to specify your own types. This version does
935 a mapping using the handler's typemap attribute, which is set up in
936 __init__() to a dictionary which contains mappings for DEBUG, INFO,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000937 WARNING, ERROR and CRITICAL. If you are using your own levels you will
Guido van Rossum57102f82002-11-13 16:15:58 +0000938 either need to override this method or place a suitable dictionary in
939 the handler's typemap attribute.
940 """
941 return self.typemap.get(record.levelno, self.deftype)
942
943 def emit(self, record):
944 """
945 Emit a record.
946
947 Determine the message ID, event category and event type. Then
948 log the message in the NT event log.
949 """
950 if self._welu:
951 try:
952 id = self.getMessageID(record)
953 cat = self.getEventCategory(record)
954 type = self.getEventType(record)
955 msg = self.format(record)
956 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000957 except (KeyboardInterrupt, SystemExit):
958 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000959 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000960 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000961
962 def close(self):
963 """
964 Clean up this handler.
965
966 You can remove the application name from the registry as a
967 source of event log entries. However, if you do this, you will
968 not be able to see the events as you intended in the Event Log
969 Viewer - it needs to be able to access the registry to get the
970 DLL name.
971 """
972 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000973 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000974
975class HTTPHandler(logging.Handler):
976 """
977 A class which sends records to a Web server, using either GET or
978 POST semantics.
979 """
980 def __init__(self, host, url, method="GET"):
981 """
982 Initialize the instance with the host, the request URL, and the method
983 ("GET" or "POST")
984 """
985 logging.Handler.__init__(self)
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000986 method = method.upper()
Guido van Rossum57102f82002-11-13 16:15:58 +0000987 if method not in ["GET", "POST"]:
Collin Winterce36ad82007-08-30 01:19:48 +0000988 raise ValueError("method must be GET or POST")
Guido van Rossum57102f82002-11-13 16:15:58 +0000989 self.host = host
990 self.url = url
991 self.method = method
992
Neal Norwitzf297bd12003-04-23 03:49:43 +0000993 def mapLogRecord(self, record):
994 """
995 Default implementation of mapping the log record into a dict
Vinay Sajip48cfe382004-02-20 13:17:27 +0000996 that is sent as the CGI data. Overwrite in your class.
Neal Norwitzf297bd12003-04-23 03:49:43 +0000997 Contributed by Franz Glasner.
998 """
999 return record.__dict__
1000
Guido van Rossum57102f82002-11-13 16:15:58 +00001001 def emit(self, record):
1002 """
1003 Emit a record.
1004
1005 Send the record to the Web server as an URL-encoded dictionary
1006 """
1007 try:
Georg Brandl029986a2008-06-23 11:44:14 +00001008 import http.client, urllib.parse
Vinay Sajipb7935062005-10-11 13:15:31 +00001009 host = self.host
Georg Brandl24420152008-05-26 16:32:26 +00001010 h = http.client.HTTP(host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001011 url = self.url
Georg Brandl029986a2008-06-23 11:44:14 +00001012 data = urllib.parse.urlencode(self.mapLogRecord(record))
Guido van Rossum57102f82002-11-13 16:15:58 +00001013 if self.method == "GET":
Neal Norwitz9d72bb42007-04-17 08:48:32 +00001014 if (url.find('?') >= 0):
Guido van Rossum57102f82002-11-13 16:15:58 +00001015 sep = '&'
1016 else:
1017 sep = '?'
1018 url = url + "%c%s" % (sep, data)
1019 h.putrequest(self.method, url)
Vinay Sajipb7935062005-10-11 13:15:31 +00001020 # support multiple hosts on one IP address...
1021 # need to strip optional :port from host, if present
Neal Norwitz9d72bb42007-04-17 08:48:32 +00001022 i = host.find(":")
Vinay Sajipb7935062005-10-11 13:15:31 +00001023 if i >= 0:
1024 host = host[:i]
1025 h.putheader("Host", host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001026 if self.method == "POST":
Vinay Sajipb7935062005-10-11 13:15:31 +00001027 h.putheader("Content-type",
1028 "application/x-www-form-urlencoded")
Guido van Rossum57102f82002-11-13 16:15:58 +00001029 h.putheader("Content-length", str(len(data)))
Benjamin Peterson458ad472009-01-18 00:08:45 +00001030 h.endheaders(data if self.method == "POST" else None)
Guido van Rossum57102f82002-11-13 16:15:58 +00001031 h.getreply() #can't do anything with the result
Vinay Sajip245a5ab2005-10-31 14:27:01 +00001032 except (KeyboardInterrupt, SystemExit):
1033 raise
Guido van Rossum57102f82002-11-13 16:15:58 +00001034 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +00001035 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +00001036
1037class BufferingHandler(logging.Handler):
1038 """
1039 A handler class which buffers logging records in memory. Whenever each
1040 record is added to the buffer, a check is made to see if the buffer should
1041 be flushed. If it should, then flush() is expected to do what's needed.
1042 """
1043 def __init__(self, capacity):
1044 """
1045 Initialize the handler with the buffer size.
1046 """
1047 logging.Handler.__init__(self)
1048 self.capacity = capacity
1049 self.buffer = []
1050
1051 def shouldFlush(self, record):
1052 """
1053 Should the handler flush its buffer?
1054
1055 Returns true if the buffer is up to capacity. This method can be
1056 overridden to implement custom flushing strategies.
1057 """
1058 return (len(self.buffer) >= self.capacity)
1059
1060 def emit(self, record):
1061 """
1062 Emit a record.
1063
1064 Append the record. If shouldFlush() tells us to, call flush() to process
1065 the buffer.
1066 """
1067 self.buffer.append(record)
1068 if self.shouldFlush(record):
1069 self.flush()
1070
1071 def flush(self):
1072 """
1073 Override to implement custom flushing behaviour.
1074
1075 This version just zaps the buffer to empty.
1076 """
1077 self.buffer = []
1078
Vinay Sajipf42d95e2004-02-21 22:14:34 +00001079 def close(self):
1080 """
1081 Close the handler.
1082
1083 This version just flushes and chains to the parent class' close().
1084 """
1085 self.flush()
1086 logging.Handler.close(self)
1087
Guido van Rossum57102f82002-11-13 16:15:58 +00001088class MemoryHandler(BufferingHandler):
1089 """
1090 A handler class which buffers logging records in memory, periodically
1091 flushing them to a target handler. Flushing occurs whenever the buffer
1092 is full, or when an event of a certain severity or greater is seen.
1093 """
1094 def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
1095 """
1096 Initialize the handler with the buffer size, the level at which
1097 flushing should occur and an optional target.
1098
1099 Note that without a target being set either here or via setTarget(),
1100 a MemoryHandler is no use to anyone!
1101 """
1102 BufferingHandler.__init__(self, capacity)
1103 self.flushLevel = flushLevel
1104 self.target = target
1105
1106 def shouldFlush(self, record):
1107 """
1108 Check for buffer full or a record at the flushLevel or higher.
1109 """
1110 return (len(self.buffer) >= self.capacity) or \
1111 (record.levelno >= self.flushLevel)
1112
1113 def setTarget(self, target):
1114 """
1115 Set the target handler for this handler.
1116 """
1117 self.target = target
1118
1119 def flush(self):
1120 """
1121 For a MemoryHandler, flushing means just sending the buffered
1122 records to the target, if there is one. Override if you want
1123 different behaviour.
1124 """
1125 if self.target:
1126 for record in self.buffer:
1127 self.target.handle(record)
1128 self.buffer = []
1129
1130 def close(self):
1131 """
1132 Flush, set the target to None and lose the buffer.
1133 """
1134 self.flush()
1135 self.target = None
Vinay Sajip48cfe382004-02-20 13:17:27 +00001136 BufferingHandler.close(self)