blob: 96cdcc65e08d7f89983c7b234823fd39f23d251b [file] [log] [blame]
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001# Copyright 2001-2010 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 Sajipdb81c4c2010-02-25 23:13:06 +000022Copyright (C) 2001-2010 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
Benjamin Peterson9451a1c2010-03-13 22:30:34 +000028from stat import ST_DEV, ST_INO, ST_MTIME
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
Vinay Sajipcbabd7e2009-10-10 20:32:36 +000044SYSLOG_TCP_PORT = 514
Guido van Rossum57102f82002-11-13 16:15:58 +000045
Thomas Wouters477c8d52006-05-27 19:21:47 +000046_MIDNIGHT = 24 * 60 * 60 # number of seconds in a day
47
Vinay Sajip17c52d82004-07-03 11:48:34 +000048class BaseRotatingHandler(logging.FileHandler):
49 """
50 Base class for handlers that rotate log files at a certain point.
51 Not meant to be instantiated directly. Instead, use RotatingFileHandler
52 or TimedRotatingFileHandler.
53 """
Christian Heimese7a15bb2008-01-24 16:21:45 +000054 def __init__(self, filename, mode, encoding=None, delay=0):
Vinay Sajip17c52d82004-07-03 11:48:34 +000055 """
56 Use the specified filename for streamed logging
57 """
Vinay Sajip4600f112005-03-13 09:56:36 +000058 if codecs is None:
59 encoding = None
Christian Heimese7a15bb2008-01-24 16:21:45 +000060 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
Vinay Sajip4600f112005-03-13 09:56:36 +000061 self.mode = mode
62 self.encoding = encoding
Guido van Rossum57102f82002-11-13 16:15:58 +000063
Vinay Sajip17c52d82004-07-03 11:48:34 +000064 def emit(self, record):
65 """
66 Emit a record.
67
68 Output the record to the file, catering for rollover as described
69 in doRollover().
70 """
Vinay Sajip3970c112004-07-08 10:24:04 +000071 try:
72 if self.shouldRollover(record):
73 self.doRollover()
74 logging.FileHandler.emit(self, record)
Vinay Sajip85c19092005-10-31 13:14:19 +000075 except (KeyboardInterrupt, SystemExit):
76 raise
Vinay Sajip3970c112004-07-08 10:24:04 +000077 except:
78 self.handleError(record)
Vinay Sajip17c52d82004-07-03 11:48:34 +000079
80class RotatingFileHandler(BaseRotatingHandler):
81 """
82 Handler for logging to a set of files, which switches from one file
83 to the next when the current file reaches a certain size.
84 """
Christian Heimese7a15bb2008-01-24 16:21:45 +000085 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0):
Guido van Rossum57102f82002-11-13 16:15:58 +000086 """
87 Open the specified file and use it as the stream for logging.
88
89 By default, the file grows indefinitely. You can specify particular
90 values of maxBytes and backupCount to allow the file to rollover at
91 a predetermined size.
92
93 Rollover occurs whenever the current log file is nearly maxBytes in
94 length. If backupCount is >= 1, the system will successively create
95 new files with the same pathname as the base file, but with extensions
96 ".1", ".2" etc. appended to it. For example, with a backupCount of 5
97 and a base file name of "app.log", you would get "app.log",
98 "app.log.1", "app.log.2", ... through to "app.log.5". The file being
99 written to is always "app.log" - when it gets filled up, it is closed
100 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
101 exist, then they are renamed to "app.log.2", "app.log.3" etc.
102 respectively.
103
104 If maxBytes is zero, rollover never occurs.
105 """
Vinay Sajip89994b22010-08-22 18:11:02 +0000106 # If rotation/rollover is wanted, it doesn't make sense to use another
107 # mode. If for example 'w' were specified, then if there were multiple
108 # runs of the calling application, the logs from previous runs would be
109 # lost if the 'w' is respected, because the log file would be truncated
110 # on each run.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000111 if maxBytes > 0:
Vinay Sajip89994b22010-08-22 18:11:02 +0000112 mode = 'a'
Christian Heimese7a15bb2008-01-24 16:21:45 +0000113 BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
Guido van Rossum57102f82002-11-13 16:15:58 +0000114 self.maxBytes = maxBytes
115 self.backupCount = backupCount
Guido van Rossum57102f82002-11-13 16:15:58 +0000116
117 def doRollover(self):
118 """
119 Do a rollover, as described in __init__().
120 """
Vinay Sajip6268cbc2009-01-21 00:19:28 +0000121 if self.stream:
122 self.stream.close()
Guido van Rossum57102f82002-11-13 16:15:58 +0000123 if self.backupCount > 0:
124 for i in range(self.backupCount - 1, 0, -1):
125 sfn = "%s.%d" % (self.baseFilename, i)
126 dfn = "%s.%d" % (self.baseFilename, i + 1)
127 if os.path.exists(sfn):
Guido van Rossum57102f82002-11-13 16:15:58 +0000128 if os.path.exists(dfn):
129 os.remove(dfn)
130 os.rename(sfn, dfn)
131 dfn = self.baseFilename + ".1"
132 if os.path.exists(dfn):
133 os.remove(dfn)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000134 os.rename(self.baseFilename, dfn)
Thomas Woutersb2137042007-02-01 18:02:27 +0000135 self.mode = 'w'
136 self.stream = self._open()
Guido van Rossum57102f82002-11-13 16:15:58 +0000137
Vinay Sajip17c52d82004-07-03 11:48:34 +0000138 def shouldRollover(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000139 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000140 Determine if rollover should occur.
Guido van Rossum57102f82002-11-13 16:15:58 +0000141
Vinay Sajip17c52d82004-07-03 11:48:34 +0000142 Basically, see if the supplied record would cause the file to exceed
143 the size limit we have.
Guido van Rossum57102f82002-11-13 16:15:58 +0000144 """
Vinay Sajip6268cbc2009-01-21 00:19:28 +0000145 if self.stream is None: # delay was set...
146 self.stream = self._open()
Guido van Rossum57102f82002-11-13 16:15:58 +0000147 if self.maxBytes > 0: # are we rolling over?
148 msg = "%s\n" % self.format(record)
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000149 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
Guido van Rossum57102f82002-11-13 16:15:58 +0000150 if self.stream.tell() + len(msg) >= self.maxBytes:
Vinay Sajip17c52d82004-07-03 11:48:34 +0000151 return 1
152 return 0
Guido van Rossum57102f82002-11-13 16:15:58 +0000153
Vinay Sajip17c52d82004-07-03 11:48:34 +0000154class TimedRotatingFileHandler(BaseRotatingHandler):
155 """
156 Handler for logging to a file, rotating the log file at certain timed
157 intervals.
158
159 If backupCount is > 0, when rollover is done, no more than backupCount
160 files are kept - the oldest ones are deleted.
161 """
Vinay Sajipcbabd7e2009-10-10 20:32:36 +0000162 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
Christian Heimese7a15bb2008-01-24 16:21:45 +0000163 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000164 self.when = when.upper()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000165 self.backupCount = backupCount
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000166 self.utc = utc
Vinay Sajip17c52d82004-07-03 11:48:34 +0000167 # Calculate the real rollover interval, which is just the number of
168 # seconds between rollovers. Also set the filename suffix used when
169 # a rollover occurs. Current 'when' events supported:
170 # S - Seconds
171 # M - Minutes
172 # H - Hours
173 # D - Days
174 # midnight - roll over at midnight
175 # W{0-6} - roll over on a certain day; 0 - Monday
176 #
177 # Case of the 'when' specifier is not important; lower or upper case
178 # will work.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000179 if self.when == 'S':
180 self.interval = 1 # one second
181 self.suffix = "%Y-%m-%d_%H-%M-%S"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000182 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000183 elif self.when == 'M':
184 self.interval = 60 # one minute
185 self.suffix = "%Y-%m-%d_%H-%M"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000186 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000187 elif self.when == 'H':
188 self.interval = 60 * 60 # one hour
189 self.suffix = "%Y-%m-%d_%H"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000190 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000191 elif self.when == 'D' or self.when == 'MIDNIGHT':
192 self.interval = 60 * 60 * 24 # one day
193 self.suffix = "%Y-%m-%d"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000194 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000195 elif self.when.startswith('W'):
196 self.interval = 60 * 60 * 24 * 7 # one week
197 if len(self.when) != 2:
198 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
199 if self.when[1] < '0' or self.when[1] > '6':
200 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
201 self.dayOfWeek = int(self.when[1])
202 self.suffix = "%Y-%m-%d"
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000203 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000204 else:
205 raise ValueError("Invalid rollover interval specified: %s" % self.when)
206
Antoine Pitroufd036452008-08-19 17:56:33 +0000207 self.extMatch = re.compile(self.extMatch, re.ASCII)
Vinay Sajipe7d40662004-10-03 19:12:07 +0000208 self.interval = self.interval * interval # multiply by units requested
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000209 if os.path.exists(filename):
210 t = os.stat(filename)[ST_MTIME]
211 else:
212 t = int(time.time())
213 self.rolloverAt = self.computeRollover(t)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000214
Vinay Sajipaffbd872009-06-11 10:11:47 +0000215 def computeRollover(self, currentTime):
216 """
217 Work out the rollover time based on the specified time.
218 """
219 result = currentTime + self.interval
Vinay Sajip17c52d82004-07-03 11:48:34 +0000220 # If we are rolling over at midnight or weekly, then the interval is already known.
221 # What we need to figure out is WHEN the next interval is. In other words,
222 # if you are rolling over at midnight, then your base interval is 1 day,
223 # but you want to start that one day clock at midnight, not now. So, we
224 # have to fudge the rolloverAt value in order to trigger the first rollover
225 # at the right time. After that, the regular interval will take care of
226 # the rest. Note that this code doesn't care about leap seconds. :)
227 if self.when == 'MIDNIGHT' or self.when.startswith('W'):
228 # This could be done with less code, but I wanted it to be clear
Vinay Sajipaffbd872009-06-11 10:11:47 +0000229 if self.utc:
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000230 t = time.gmtime(currentTime)
231 else:
232 t = time.localtime(currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000233 currentHour = t[3]
234 currentMinute = t[4]
235 currentSecond = t[5]
236 # r is the number of seconds left between now and midnight
Thomas Wouters477c8d52006-05-27 19:21:47 +0000237 r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
238 currentSecond)
Vinay Sajipaffbd872009-06-11 10:11:47 +0000239 result = currentTime + r
Vinay Sajip17c52d82004-07-03 11:48:34 +0000240 # If we are rolling over on a certain day, add in the number of days until
241 # the next rollover, but offset by 1 since we just calculated the time
242 # until the next day starts. There are three cases:
243 # Case 1) The day to rollover is today; in this case, do nothing
244 # Case 2) The day to rollover is further in the interval (i.e., today is
245 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
246 # next rollover is simply 6 - 2 - 1, or 3.
247 # Case 3) The day to rollover is behind us in the interval (i.e., today
248 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
249 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
250 # number of days left in the current week (1) plus the number
251 # of days in the next week until the rollover day (3).
Georg Brandl86def6c2008-01-21 20:36:10 +0000252 # The calculations described in 2) and 3) above need to have a day added.
253 # This is because the above time calculation takes us to midnight on this
254 # day, i.e. the start of the next day.
Vinay Sajipaffbd872009-06-11 10:11:47 +0000255 if self.when.startswith('W'):
Vinay Sajip17c52d82004-07-03 11:48:34 +0000256 day = t[6] # 0 is Monday
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000257 if day != self.dayOfWeek:
258 if day < self.dayOfWeek:
Georg Brandl86def6c2008-01-21 20:36:10 +0000259 daysToWait = self.dayOfWeek - day
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000260 else:
Georg Brandl86def6c2008-01-21 20:36:10 +0000261 daysToWait = 6 - day + self.dayOfWeek + 1
Vinay Sajipaffbd872009-06-11 10:11:47 +0000262 newRolloverAt = result + (daysToWait * (60 * 60 * 24))
263 if not self.utc:
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000264 dstNow = t[-1]
265 dstAtRollover = time.localtime(newRolloverAt)[-1]
266 if dstNow != dstAtRollover:
267 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
268 newRolloverAt = newRolloverAt - 3600
269 else: # DST bows out before next rollover, so we need to add an hour
270 newRolloverAt = newRolloverAt + 3600
Vinay Sajipaffbd872009-06-11 10:11:47 +0000271 result = newRolloverAt
272 return result
Vinay Sajip17c52d82004-07-03 11:48:34 +0000273
274 def shouldRollover(self, record):
275 """
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000276 Determine if rollover should occur.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000277
278 record is not used, as we are just comparing times, but it is needed so
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000279 the method signatures are the same
Vinay Sajip17c52d82004-07-03 11:48:34 +0000280 """
281 t = int(time.time())
282 if t >= self.rolloverAt:
283 return 1
Vinay Sajip17c52d82004-07-03 11:48:34 +0000284 return 0
285
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000286 def getFilesToDelete(self):
287 """
288 Determine the files to delete when rolling over.
289
290 More specific than the earlier method, which just used glob.glob().
291 """
292 dirName, baseName = os.path.split(self.baseFilename)
293 fileNames = os.listdir(dirName)
294 result = []
295 prefix = baseName + "."
296 plen = len(prefix)
297 for fileName in fileNames:
298 if fileName[:plen] == prefix:
299 suffix = fileName[plen:]
300 if self.extMatch.match(suffix):
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000301 result.append(os.path.join(dirName, fileName))
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000302 result.sort()
303 if len(result) < self.backupCount:
304 result = []
305 else:
306 result = result[:len(result) - self.backupCount]
307 return result
308
Vinay Sajip17c52d82004-07-03 11:48:34 +0000309 def doRollover(self):
310 """
311 do a rollover; in this case, a date/time stamp is appended to the filename
312 when the rollover happens. However, you want the file to be named for the
313 start of the interval, not the current time. If there is a backup count,
314 then we have to get a list of matching filenames, sort them and remove
315 the one with the oldest suffix.
316 """
Vinay Sajip6268cbc2009-01-21 00:19:28 +0000317 if self.stream:
318 self.stream.close()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000319 # get the time that this sequence started at and make it a TimeTuple
320 t = self.rolloverAt - self.interval
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000321 if self.utc:
322 timeTuple = time.gmtime(t)
323 else:
324 timeTuple = time.localtime(t)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000325 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
326 if os.path.exists(dfn):
327 os.remove(dfn)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000328 os.rename(self.baseFilename, dfn)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000329 if self.backupCount > 0:
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000330 for s in self.getFilesToDelete():
331 os.remove(s)
Thomas Woutersb2137042007-02-01 18:02:27 +0000332 self.mode = 'w'
333 self.stream = self._open()
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000334 currentTime = int(time.time())
Vinay Sajipaffbd872009-06-11 10:11:47 +0000335 newRolloverAt = self.computeRollover(currentTime)
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000336 while newRolloverAt <= currentTime:
337 newRolloverAt = newRolloverAt + self.interval
338 #If DST changes and midnight or weekly rollover, adjust for this.
Benjamin Petersona37cfc62008-05-26 13:48:34 +0000339 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
Benjamin Petersonad9d48d2008-04-02 21:49:44 +0000340 dstNow = time.localtime(currentTime)[-1]
341 dstAtRollover = time.localtime(newRolloverAt)[-1]
342 if dstNow != dstAtRollover:
343 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
344 newRolloverAt = newRolloverAt - 3600
345 else: # DST bows out before next rollover, so we need to add an hour
346 newRolloverAt = newRolloverAt + 3600
347 self.rolloverAt = newRolloverAt
Guido van Rossum57102f82002-11-13 16:15:58 +0000348
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000349class WatchedFileHandler(logging.FileHandler):
350 """
351 A handler for logging to a file, which watches the file
352 to see if it has changed while in use. This can happen because of
353 usage of programs such as newsyslog and logrotate which perform
354 log file rotation. This handler, intended for use under Unix,
355 watches the file to see if it has changed since the last emit.
356 (A file has changed if its device or inode have changed.)
357 If it has changed, the old file stream is closed, and the file
358 opened to get a new stream.
359
360 This handler is not appropriate for use under Windows, because
361 under Windows open files cannot be moved or renamed - logging
362 opens the files with exclusive locks - and so there is no need
363 for such a handler. Furthermore, ST_INO is not supported under
364 Windows; stat always returns zero for this value.
365
366 This handler is based on a suggestion and patch by Chad J.
367 Schroeder.
368 """
Christian Heimese7a15bb2008-01-24 16:21:45 +0000369 def __init__(self, filename, mode='a', encoding=None, delay=0):
370 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
371 if not os.path.exists(self.baseFilename):
372 self.dev, self.ino = -1, -1
373 else:
374 stat = os.stat(self.baseFilename)
375 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000376
377 def emit(self, record):
378 """
379 Emit a record.
380
381 First check if the underlying file has changed, and if it
382 has, close the old stream and reopen the file to get the
383 current stream.
384 """
385 if not os.path.exists(self.baseFilename):
386 stat = None
387 changed = 1
388 else:
389 stat = os.stat(self.baseFilename)
390 changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
Christian Heimese7a15bb2008-01-24 16:21:45 +0000391 if changed and self.stream is not None:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000392 self.stream.flush()
393 self.stream.close()
394 self.stream = self._open()
395 if stat is None:
396 stat = os.stat(self.baseFilename)
397 self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
398 logging.FileHandler.emit(self, record)
399
Guido van Rossum57102f82002-11-13 16:15:58 +0000400class SocketHandler(logging.Handler):
401 """
402 A handler class which writes logging records, in pickle format, to
403 a streaming socket. The socket is kept open across logging calls.
404 If the peer resets it, an attempt is made to reconnect on the next call.
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000405 The pickle which is sent is that of the LogRecord's attribute dictionary
406 (__dict__), so that the receiver does not need to have the logging module
407 installed in order to process the logging event.
408
409 To unpickle the record at the receiving end into a LogRecord, use the
410 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000411 """
412
413 def __init__(self, host, port):
414 """
415 Initializes the handler with a specific host address and port.
416
417 The attribute 'closeOnError' is set to 1 - which means that if
418 a socket error occurs, the socket is silently closed and then
419 reopened on the next logging call.
420 """
421 logging.Handler.__init__(self)
422 self.host = host
423 self.port = port
424 self.sock = None
425 self.closeOnError = 0
Vinay Sajip48cfe382004-02-20 13:17:27 +0000426 self.retryTime = None
427 #
428 # Exponential backoff parameters.
429 #
430 self.retryStart = 1.0
431 self.retryMax = 30.0
432 self.retryFactor = 2.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000433
Guido van Rossumd8faa362007-04-27 19:54:29 +0000434 def makeSocket(self, timeout=1):
Guido van Rossum57102f82002-11-13 16:15:58 +0000435 """
436 A factory method which allows subclasses to define the precise
437 type of socket they want.
438 """
439 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000440 if hasattr(s, 'settimeout'):
441 s.settimeout(timeout)
Guido van Rossum57102f82002-11-13 16:15:58 +0000442 s.connect((self.host, self.port))
443 return s
444
Vinay Sajip48cfe382004-02-20 13:17:27 +0000445 def createSocket(self):
446 """
447 Try to create a socket, using an exponential backoff with
448 a max retry time. Thanks to Robert Olson for the original patch
449 (SF #815911) which has been slightly refactored.
450 """
451 now = time.time()
452 # Either retryTime is None, in which case this
453 # is the first time back after a disconnect, or
454 # we've waited long enough.
455 if self.retryTime is None:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000456 attempt = 1
Vinay Sajip48cfe382004-02-20 13:17:27 +0000457 else:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000458 attempt = (now >= self.retryTime)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000459 if attempt:
460 try:
461 self.sock = self.makeSocket()
462 self.retryTime = None # next time, no delay before trying
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000463 except socket.error:
Vinay Sajip48cfe382004-02-20 13:17:27 +0000464 #Creation failed, so set the retry time and return.
465 if self.retryTime is None:
466 self.retryPeriod = self.retryStart
467 else:
468 self.retryPeriod = self.retryPeriod * self.retryFactor
469 if self.retryPeriod > self.retryMax:
470 self.retryPeriod = self.retryMax
471 self.retryTime = now + self.retryPeriod
472
Guido van Rossum57102f82002-11-13 16:15:58 +0000473 def send(self, s):
474 """
475 Send a pickled string to the socket.
476
477 This function allows for partial sends which can happen when the
478 network is busy.
479 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000480 if self.sock is None:
481 self.createSocket()
482 #self.sock can be None either because we haven't reached the retry
483 #time yet, or because we have reached the retry time and retried,
484 #but are still unable to connect.
485 if self.sock:
486 try:
487 if hasattr(self.sock, "sendall"):
488 self.sock.sendall(s)
489 else:
490 sentsofar = 0
491 left = len(s)
492 while left > 0:
493 sent = self.sock.send(s[sentsofar:])
494 sentsofar = sentsofar + sent
495 left = left - sent
496 except socket.error:
497 self.sock.close()
498 self.sock = None # so we can call createSocket next time
Guido van Rossum57102f82002-11-13 16:15:58 +0000499
500 def makePickle(self, record):
501 """
502 Pickles the record in binary format with a length prefix, and
503 returns it ready for transmission across the socket.
504 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000505 ei = record.exc_info
506 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000507 dummy = self.format(record) # just to get traceback text into record.exc_text
508 record.exc_info = None # to avoid Unpickleable error
Guido van Rossumba205d62006-08-17 08:57:26 +0000509 s = pickle.dumps(record.__dict__, 1)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000510 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000511 record.exc_info = ei # for next handler
Guido van Rossum57102f82002-11-13 16:15:58 +0000512 slen = struct.pack(">L", len(s))
513 return slen + s
514
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000515 def handleError(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000516 """
517 Handle an error during logging.
518
519 An error has occurred during logging. Most likely cause -
520 connection lost. Close the socket so that we can retry on the
521 next event.
522 """
523 if self.closeOnError and self.sock:
524 self.sock.close()
525 self.sock = None #try to reconnect next time
526 else:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000527 logging.Handler.handleError(self, record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000528
529 def emit(self, record):
530 """
531 Emit a record.
532
533 Pickles the record and writes it to the socket in binary format.
534 If there is an error with the socket, silently drop the packet.
535 If there was a problem with the socket, re-establishes the
536 socket.
537 """
538 try:
539 s = self.makePickle(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000540 self.send(s)
Vinay Sajip85c19092005-10-31 13:14:19 +0000541 except (KeyboardInterrupt, SystemExit):
542 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000543 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000544 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000545
546 def close(self):
547 """
548 Closes the socket.
549 """
550 if self.sock:
551 self.sock.close()
552 self.sock = None
Vinay Sajip48cfe382004-02-20 13:17:27 +0000553 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000554
555class DatagramHandler(SocketHandler):
556 """
557 A handler class which writes logging records, in pickle format, to
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000558 a datagram socket. The pickle which is sent is that of the LogRecord's
559 attribute dictionary (__dict__), so that the receiver does not need to
560 have the logging module installed in order to process the logging event.
561
562 To unpickle the record at the receiving end into a LogRecord, use the
563 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000564
565 """
566 def __init__(self, host, port):
567 """
568 Initializes the handler with a specific host address and port.
569 """
570 SocketHandler.__init__(self, host, port)
571 self.closeOnError = 0
572
573 def makeSocket(self):
574 """
575 The factory method of SocketHandler is here overridden to create
576 a UDP socket (SOCK_DGRAM).
577 """
578 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
579 return s
580
581 def send(self, s):
582 """
583 Send a pickled string to a socket.
584
585 This function no longer allows for partial sends which can happen
586 when the network is busy - UDP does not guarantee delivery and
587 can deliver packets out of sequence.
588 """
Vinay Sajipfb154172004-08-24 09:36:23 +0000589 if self.sock is None:
590 self.createSocket()
Guido van Rossum57102f82002-11-13 16:15:58 +0000591 self.sock.sendto(s, (self.host, self.port))
592
593class SysLogHandler(logging.Handler):
594 """
595 A handler class which sends formatted logging records to a syslog
596 server. Based on Sam Rushing's syslog module:
597 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
598 Contributed by Nicolas Untz (after which minor refactoring changes
599 have been made).
600 """
601
602 # from <linux/sys/syslog.h>:
603 # ======================================================================
604 # priorities/facilities are encoded into a single 32-bit quantity, where
605 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
606 # facility (0-big number). Both the priorities and the facilities map
607 # roughly one-to-one to strings in the syslogd(8) source code. This
608 # mapping is included in this file.
609 #
610 # priorities (these are ordered)
611
612 LOG_EMERG = 0 # system is unusable
613 LOG_ALERT = 1 # action must be taken immediately
614 LOG_CRIT = 2 # critical conditions
615 LOG_ERR = 3 # error conditions
616 LOG_WARNING = 4 # warning conditions
617 LOG_NOTICE = 5 # normal but significant condition
618 LOG_INFO = 6 # informational
619 LOG_DEBUG = 7 # debug-level messages
620
621 # facility codes
622 LOG_KERN = 0 # kernel messages
623 LOG_USER = 1 # random user-level messages
624 LOG_MAIL = 2 # mail system
625 LOG_DAEMON = 3 # system daemons
626 LOG_AUTH = 4 # security/authorization messages
627 LOG_SYSLOG = 5 # messages generated internally by syslogd
628 LOG_LPR = 6 # line printer subsystem
629 LOG_NEWS = 7 # network news subsystem
630 LOG_UUCP = 8 # UUCP subsystem
631 LOG_CRON = 9 # clock daemon
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000632 LOG_AUTHPRIV = 10 # security/authorization messages (private)
633 LOG_FTP = 11 # FTP daemon
Guido van Rossum57102f82002-11-13 16:15:58 +0000634
635 # other codes through 15 reserved for system use
636 LOG_LOCAL0 = 16 # reserved for local use
637 LOG_LOCAL1 = 17 # reserved for local use
638 LOG_LOCAL2 = 18 # reserved for local use
639 LOG_LOCAL3 = 19 # reserved for local use
640 LOG_LOCAL4 = 20 # reserved for local use
641 LOG_LOCAL5 = 21 # reserved for local use
642 LOG_LOCAL6 = 22 # reserved for local use
643 LOG_LOCAL7 = 23 # reserved for local use
644
645 priority_names = {
646 "alert": LOG_ALERT,
647 "crit": LOG_CRIT,
648 "critical": LOG_CRIT,
649 "debug": LOG_DEBUG,
650 "emerg": LOG_EMERG,
651 "err": LOG_ERR,
652 "error": LOG_ERR, # DEPRECATED
653 "info": LOG_INFO,
654 "notice": LOG_NOTICE,
655 "panic": LOG_EMERG, # DEPRECATED
656 "warn": LOG_WARNING, # DEPRECATED
657 "warning": LOG_WARNING,
658 }
659
660 facility_names = {
661 "auth": LOG_AUTH,
662 "authpriv": LOG_AUTHPRIV,
663 "cron": LOG_CRON,
664 "daemon": LOG_DAEMON,
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000665 "ftp": LOG_FTP,
Guido van Rossum57102f82002-11-13 16:15:58 +0000666 "kern": LOG_KERN,
667 "lpr": LOG_LPR,
668 "mail": LOG_MAIL,
669 "news": LOG_NEWS,
670 "security": LOG_AUTH, # DEPRECATED
671 "syslog": LOG_SYSLOG,
672 "user": LOG_USER,
673 "uucp": LOG_UUCP,
674 "local0": LOG_LOCAL0,
675 "local1": LOG_LOCAL1,
676 "local2": LOG_LOCAL2,
677 "local3": LOG_LOCAL3,
678 "local4": LOG_LOCAL4,
679 "local5": LOG_LOCAL5,
680 "local6": LOG_LOCAL6,
681 "local7": LOG_LOCAL7,
682 }
683
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000684 #The map below appears to be trivially lowercasing the key. However,
685 #there's more to it than meets the eye - in some locales, lowercasing
686 #gives unexpected results. See SF #1524081: in the Turkish locale,
687 #"INFO".lower() != "info"
688 priority_map = {
689 "DEBUG" : "debug",
690 "INFO" : "info",
691 "WARNING" : "warning",
692 "ERROR" : "error",
693 "CRITICAL" : "critical"
694 }
695
Vinay Sajipcbabd7e2009-10-10 20:32:36 +0000696 def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
697 facility=LOG_USER, socktype=socket.SOCK_DGRAM):
Guido van Rossum57102f82002-11-13 16:15:58 +0000698 """
699 Initialize a handler.
700
Guido van Rossume7ba4952007-06-06 23:52:48 +0000701 If address is specified as a string, a UNIX socket is used. To log to a
702 local syslogd, "SysLogHandler(address="/dev/log")" can be used.
Guido van Rossum57102f82002-11-13 16:15:58 +0000703 If facility is not specified, LOG_USER is used.
704 """
705 logging.Handler.__init__(self)
706
707 self.address = address
708 self.facility = facility
Vinay Sajipcbabd7e2009-10-10 20:32:36 +0000709 self.socktype = socktype
710
Guido van Rossum13257902007-06-07 23:15:56 +0000711 if isinstance(address, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000712 self.unixsocket = 1
Thomas Wouters89f507f2006-12-13 04:49:30 +0000713 self._connect_unixsocket(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000714 else:
Guido van Rossum57102f82002-11-13 16:15:58 +0000715 self.unixsocket = 0
Vinay Sajipcbabd7e2009-10-10 20:32:36 +0000716 self.socket = socket.socket(socket.AF_INET, socktype)
717 if socktype == socket.SOCK_STREAM:
718 self.socket.connect(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000719 self.formatter = None
720
Vinay Sajipa1974c12005-01-13 08:23:56 +0000721 def _connect_unixsocket(self, address):
722 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
723 # syslog may require either DGRAM or STREAM sockets
724 try:
725 self.socket.connect(address)
726 except socket.error:
727 self.socket.close()
728 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Vinay Sajip8b6b53f2005-11-09 13:55:13 +0000729 self.socket.connect(address)
Vinay Sajipa1974c12005-01-13 08:23:56 +0000730
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000731 def encodePriority(self, facility, priority):
Guido van Rossum57102f82002-11-13 16:15:58 +0000732 """
733 Encode the facility and priority. You can pass in strings or
734 integers - if strings are passed, the facility_names and
735 priority_names mapping dictionaries are used to convert them to
736 integers.
737 """
Guido van Rossum13257902007-06-07 23:15:56 +0000738 if isinstance(facility, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000739 facility = self.facility_names[facility]
Guido van Rossum13257902007-06-07 23:15:56 +0000740 if isinstance(priority, str):
Guido van Rossum57102f82002-11-13 16:15:58 +0000741 priority = self.priority_names[priority]
742 return (facility << 3) | priority
743
744 def close (self):
745 """
746 Closes the socket.
747 """
748 if self.unixsocket:
749 self.socket.close()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000750 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000751
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000752 def mapPriority(self, levelName):
753 """
754 Map a logging level name to a key in the priority_names map.
755 This is useful in two scenarios: when custom levels are being
756 used, and in the case where you can't do a straightforward
757 mapping by lowercasing the logging level name because of locale-
758 specific issues (see SF #1524081).
759 """
760 return self.priority_map.get(levelName, "warning")
761
Guido van Rossum57102f82002-11-13 16:15:58 +0000762 def emit(self, record):
763 """
764 Emit a record.
765
766 The record is formatted, and then sent to the syslog server. If
767 exception information is present, it is NOT sent to the server.
768 """
Vinay Sajip89d8f822010-08-20 08:43:22 +0000769 msg = self.format(record) + '\000'
Guido van Rossum57102f82002-11-13 16:15:58 +0000770 """
771 We need to convert record level to lowercase, maybe this will
772 change in the future.
773 """
Vinay Sajip467d12f2010-08-19 22:20:22 +0000774 prio = '<%d>' % self.encodePriority(self.facility,
775 self.mapPriority(record.levelname))
776 prio = prio.encode('utf-8')
Vinay Sajip609364a2010-08-30 18:31:13 +0000777 # Message is a string. Convert to bytes as required by RFC 5424
Vinay Sajip42ead482009-10-21 20:22:14 +0000778 msg = msg.encode('utf-8')
779 if codecs:
780 msg = codecs.BOM_UTF8 + msg
Vinay Sajip467d12f2010-08-19 22:20:22 +0000781 msg = prio + msg
Guido van Rossum57102f82002-11-13 16:15:58 +0000782 try:
783 if self.unixsocket:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000784 try:
785 self.socket.send(msg)
786 except socket.error:
787 self._connect_unixsocket(self.address)
788 self.socket.send(msg)
Vinay Sajipcbabd7e2009-10-10 20:32:36 +0000789 elif self.socktype == socket.SOCK_DGRAM:
Guido van Rossum57102f82002-11-13 16:15:58 +0000790 self.socket.sendto(msg, self.address)
Vinay Sajipcbabd7e2009-10-10 20:32:36 +0000791 else:
792 self.socket.sendall(msg)
Vinay Sajip85c19092005-10-31 13:14:19 +0000793 except (KeyboardInterrupt, SystemExit):
794 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000795 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000796 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000797
798class SMTPHandler(logging.Handler):
799 """
800 A handler class which sends an SMTP email for each logging event.
801 """
Vinay Sajip540f2152009-12-06 17:57:11 +0000802 def __init__(self, mailhost, fromaddr, toaddrs, subject,
Vinay Sajip25fcd222009-12-06 18:05:04 +0000803 credentials=None, secure=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000804 """
805 Initialize the handler.
806
807 Initialize the instance with the from and to addresses and subject
808 line of the email. To specify a non-standard SMTP port, use the
Guido van Rossum360e4b82007-05-14 22:51:27 +0000809 (host, port) tuple format for the mailhost argument. To specify
810 authentication credentials, supply a (username, password) tuple
Vinay Sajip25fcd222009-12-06 18:05:04 +0000811 for the credentials argument. To specify the use of a secure
812 protocol (TLS), pass in a tuple for the secure argument. This will
813 only be used when authentication credentials are supplied. The tuple
814 will be either an empty tuple, or a single-value tuple with the name
815 of a keyfile, or a 2-value tuple with the names of the keyfile and
816 certificate file. (This tuple is passed to the `starttls` method).
Guido van Rossum57102f82002-11-13 16:15:58 +0000817 """
818 logging.Handler.__init__(self)
Guido van Rossum13257902007-06-07 23:15:56 +0000819 if isinstance(mailhost, tuple):
Guido van Rossum360e4b82007-05-14 22:51:27 +0000820 self.mailhost, self.mailport = mailhost
Guido van Rossum57102f82002-11-13 16:15:58 +0000821 else:
Guido van Rossum360e4b82007-05-14 22:51:27 +0000822 self.mailhost, self.mailport = mailhost, None
Guido van Rossum13257902007-06-07 23:15:56 +0000823 if isinstance(credentials, tuple):
Guido van Rossum360e4b82007-05-14 22:51:27 +0000824 self.username, self.password = credentials
825 else:
826 self.username = None
Guido van Rossum57102f82002-11-13 16:15:58 +0000827 self.fromaddr = fromaddr
Guido van Rossum13257902007-06-07 23:15:56 +0000828 if isinstance(toaddrs, str):
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000829 toaddrs = [toaddrs]
Guido van Rossum57102f82002-11-13 16:15:58 +0000830 self.toaddrs = toaddrs
831 self.subject = subject
Vinay Sajip540f2152009-12-06 17:57:11 +0000832 self.secure = secure
Guido van Rossum57102f82002-11-13 16:15:58 +0000833
834 def getSubject(self, record):
835 """
836 Determine the subject for the email.
837
838 If you want to specify a subject line which is record-dependent,
839 override this method.
840 """
841 return self.subject
842
843 def emit(self, record):
844 """
845 Emit a record.
846
847 Format the record and send it to the specified addressees.
848 """
849 try:
850 import smtplib
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000851 from email.utils import formatdate
Guido van Rossum57102f82002-11-13 16:15:58 +0000852 port = self.mailport
853 if not port:
854 port = smtplib.SMTP_PORT
855 smtp = smtplib.SMTP(self.mailhost, port)
856 msg = self.format(record)
Neal Norwitzf297bd12003-04-23 03:49:43 +0000857 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 +0000858 self.fromaddr,
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000859 ",".join(self.toaddrs),
Neal Norwitzf297bd12003-04-23 03:49:43 +0000860 self.getSubject(record),
Martin v. Löwis318a12e2004-08-18 12:27:40 +0000861 formatdate(), msg)
Guido van Rossum360e4b82007-05-14 22:51:27 +0000862 if self.username:
Vinay Sajip25fcd222009-12-06 18:05:04 +0000863 if self.secure is not None:
Vinay Sajip540f2152009-12-06 17:57:11 +0000864 smtp.ehlo()
Vinay Sajip25fcd222009-12-06 18:05:04 +0000865 smtp.starttls(*self.secure)
Vinay Sajip540f2152009-12-06 17:57:11 +0000866 smtp.ehlo()
Guido van Rossum360e4b82007-05-14 22:51:27 +0000867 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 """
Vinay Sajipaf9d10a2010-09-13 20:40:30 +0000980 def __init__(self, host, url, method="GET", secure=False, credentials=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000981 """
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
Vinay Sajipaf9d10a2010-09-13 20:40:30 +0000992 self.secure = secure
993 self.credentials = credentials
Guido van Rossum57102f82002-11-13 16:15:58 +0000994
Neal Norwitzf297bd12003-04-23 03:49:43 +0000995 def mapLogRecord(self, record):
996 """
997 Default implementation of mapping the log record into a dict
Vinay Sajip48cfe382004-02-20 13:17:27 +0000998 that is sent as the CGI data. Overwrite in your class.
Vinay Sajipaf9d10a2010-09-13 20:40:30 +0000999 Contributed by Franz Glasner.
Neal Norwitzf297bd12003-04-23 03:49:43 +00001000 """
1001 return record.__dict__
1002
Guido van Rossum57102f82002-11-13 16:15:58 +00001003 def emit(self, record):
1004 """
1005 Emit a record.
1006
Senthil Kumaran30e86a42010-08-09 20:01:35 +00001007 Send the record to the Web server as a percent-encoded dictionary
Guido van Rossum57102f82002-11-13 16:15:58 +00001008 """
1009 try:
Georg Brandl029986a2008-06-23 11:44:14 +00001010 import http.client, urllib.parse
Vinay Sajipb7935062005-10-11 13:15:31 +00001011 host = self.host
Vinay Sajipaf9d10a2010-09-13 20:40:30 +00001012 if self.secure:
1013 h = http.client.HTTPSConnection(host)
1014 else:
1015 h = http.client.HTTPConnection(host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001016 url = self.url
Georg Brandl029986a2008-06-23 11:44:14 +00001017 data = urllib.parse.urlencode(self.mapLogRecord(record))
Guido van Rossum57102f82002-11-13 16:15:58 +00001018 if self.method == "GET":
Neal Norwitz9d72bb42007-04-17 08:48:32 +00001019 if (url.find('?') >= 0):
Guido van Rossum57102f82002-11-13 16:15:58 +00001020 sep = '&'
1021 else:
1022 sep = '?'
1023 url = url + "%c%s" % (sep, data)
1024 h.putrequest(self.method, url)
Vinay Sajipb7935062005-10-11 13:15:31 +00001025 # support multiple hosts on one IP address...
1026 # need to strip optional :port from host, if present
Neal Norwitz9d72bb42007-04-17 08:48:32 +00001027 i = host.find(":")
Vinay Sajipb7935062005-10-11 13:15:31 +00001028 if i >= 0:
1029 host = host[:i]
1030 h.putheader("Host", host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001031 if self.method == "POST":
Vinay Sajipb7935062005-10-11 13:15:31 +00001032 h.putheader("Content-type",
1033 "application/x-www-form-urlencoded")
Guido van Rossum57102f82002-11-13 16:15:58 +00001034 h.putheader("Content-length", str(len(data)))
Vinay Sajipaf9d10a2010-09-13 20:40:30 +00001035 if self.credentials:
1036 import base64
1037 s = ('u%s:%s' % self.credentials).encode('utf-8')
1038 s = 'Basic ' + base64.b64encode(s).strip()
1039 h.putheader('Authorization', s)
Benjamin Peterson458ad472009-01-18 00:08:45 +00001040 h.endheaders(data if self.method == "POST" else None)
Vinay Sajipaf9d10a2010-09-13 20:40:30 +00001041 h.getresponse() #can't do anything with the result
Vinay Sajip245a5ab2005-10-31 14:27:01 +00001042 except (KeyboardInterrupt, SystemExit):
1043 raise
Guido van Rossum57102f82002-11-13 16:15:58 +00001044 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +00001045 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +00001046
1047class BufferingHandler(logging.Handler):
1048 """
1049 A handler class which buffers logging records in memory. Whenever each
1050 record is added to the buffer, a check is made to see if the buffer should
1051 be flushed. If it should, then flush() is expected to do what's needed.
1052 """
1053 def __init__(self, capacity):
1054 """
1055 Initialize the handler with the buffer size.
1056 """
1057 logging.Handler.__init__(self)
1058 self.capacity = capacity
1059 self.buffer = []
1060
1061 def shouldFlush(self, record):
1062 """
1063 Should the handler flush its buffer?
1064
1065 Returns true if the buffer is up to capacity. This method can be
1066 overridden to implement custom flushing strategies.
1067 """
1068 return (len(self.buffer) >= self.capacity)
1069
1070 def emit(self, record):
1071 """
1072 Emit a record.
1073
1074 Append the record. If shouldFlush() tells us to, call flush() to process
1075 the buffer.
1076 """
1077 self.buffer.append(record)
1078 if self.shouldFlush(record):
1079 self.flush()
1080
1081 def flush(self):
1082 """
1083 Override to implement custom flushing behaviour.
1084
1085 This version just zaps the buffer to empty.
1086 """
1087 self.buffer = []
1088
Vinay Sajipf42d95e2004-02-21 22:14:34 +00001089 def close(self):
1090 """
1091 Close the handler.
1092
1093 This version just flushes and chains to the parent class' close().
1094 """
1095 self.flush()
1096 logging.Handler.close(self)
1097
Guido van Rossum57102f82002-11-13 16:15:58 +00001098class MemoryHandler(BufferingHandler):
1099 """
1100 A handler class which buffers logging records in memory, periodically
1101 flushing them to a target handler. Flushing occurs whenever the buffer
1102 is full, or when an event of a certain severity or greater is seen.
1103 """
1104 def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
1105 """
1106 Initialize the handler with the buffer size, the level at which
1107 flushing should occur and an optional target.
1108
1109 Note that without a target being set either here or via setTarget(),
1110 a MemoryHandler is no use to anyone!
1111 """
1112 BufferingHandler.__init__(self, capacity)
1113 self.flushLevel = flushLevel
1114 self.target = target
1115
1116 def shouldFlush(self, record):
1117 """
1118 Check for buffer full or a record at the flushLevel or higher.
1119 """
1120 return (len(self.buffer) >= self.capacity) or \
1121 (record.levelno >= self.flushLevel)
1122
1123 def setTarget(self, target):
1124 """
1125 Set the target handler for this handler.
1126 """
1127 self.target = target
1128
1129 def flush(self):
1130 """
1131 For a MemoryHandler, flushing means just sending the buffered
1132 records to the target, if there is one. Override if you want
1133 different behaviour.
Vinay Sajipc84f0162010-09-21 11:25:39 +00001134
1135 The record buffer is also cleared by this operation.
Guido van Rossum57102f82002-11-13 16:15:58 +00001136 """
1137 if self.target:
1138 for record in self.buffer:
1139 self.target.handle(record)
1140 self.buffer = []
1141
1142 def close(self):
1143 """
1144 Flush, set the target to None and lose the buffer.
1145 """
1146 self.flush()
1147 self.target = None
Vinay Sajip48cfe382004-02-20 13:17:27 +00001148 BufferingHandler.close(self)
Vinay Sajip121a1c42010-09-08 10:46:15 +00001149
1150
1151class QueueHandler(logging.Handler):
1152 """
1153 This handler sends events to a queue. Typically, it would be used together
1154 with a multiprocessing Queue to centralise logging to file in one process
1155 (in a multi-process application), so as to avoid file write contention
1156 between processes.
1157
1158 This code is new in Python 3.2, but this class can be copy pasted into
1159 user code for use with earlier Python versions.
1160 """
1161
1162 def __init__(self, queue):
1163 """
1164 Initialise an instance, using the passed queue.
1165 """
1166 logging.Handler.__init__(self)
1167 self.queue = queue
1168
1169 def enqueue(self, record):
1170 """
1171 Enqueue a record.
1172
1173 The base implementation uses put_nowait. You may want to override
1174 this method if you want to use blocking, timeouts or custom queue
1175 implementations.
1176 """
1177 self.queue.put_nowait(record)
1178
Vinay Sajip0258ce82010-09-22 20:34:53 +00001179 def prepare(self, record):
1180 """
Vinay Sajip0637d492010-09-23 08:15:54 +00001181 Prepares a record for queuing. The object returned by this method is
1182 enqueued.
Vinay Sajip0258ce82010-09-22 20:34:53 +00001183
1184 The base implementation formats the record to merge the message
1185 and arguments, and removes unpickleable items from the record
1186 in-place.
1187
1188 You might want to override this method if you want to convert
1189 the record to a dict or JSON string, or send a modified copy
1190 of the record while leaving the original intact.
1191 """
1192 # The format operation gets traceback text into record.exc_text
1193 # (if there's exception data), and also puts the message into
1194 # record.message. We can then use this to replace the original
1195 # msg + args, as these might be unpickleable. We also zap the
1196 # exc_info attribute, as it's no longer needed and, if not None,
1197 # will typically not be pickleable.
1198 self.format(record)
1199 record.msg = record.message
1200 record.args = None
1201 record.exc_info = None
1202 return record
1203
Vinay Sajip121a1c42010-09-08 10:46:15 +00001204 def emit(self, record):
1205 """
1206 Emit a record.
1207
Vinay Sajip0637d492010-09-23 08:15:54 +00001208 Writes the LogRecord to the queue, preparing it for pickling first.
Vinay Sajip121a1c42010-09-08 10:46:15 +00001209 """
1210 try:
Vinay Sajip0258ce82010-09-22 20:34:53 +00001211 self.enqueue(self.prepare(record))
Vinay Sajip121a1c42010-09-08 10:46:15 +00001212 except (KeyboardInterrupt, SystemExit):
1213 raise
1214 except:
1215 self.handleError(record)
Vinay Sajip0637d492010-09-23 08:15:54 +00001216
1217class QueueListener(object):
1218 """
1219 This class implements an internal threaded listener which watches for
1220 LogRecords being added to a queue, removes them and passes them to a
1221 list of handlers for processing.
1222 """
1223 _sentinel = None
1224
1225 def __init__(self, queue, *handlers):
1226 """
1227 Initialise an instance with the specified queue and
1228 handlers.
1229 """
1230 self.queue = queue
1231 self.handlers = handlers
1232 self._stop = threading.Event()
1233 self._thread = None
1234
1235 def dequeue(self, block):
1236 """
1237 Dequeue a record and return it, optionally blocking.
1238
1239 The base implementation uses get. You may want to override this method
1240 if you want to use timeouts or work with custom queue implementations.
1241 """
1242 return self.queue.get(block)
1243
1244 def start(self):
1245 """
1246 Start the listener.
1247
1248 This starts up a background thread to monitor the queue for
1249 LogRecords to process.
1250 """
1251 self._thread = t = threading.Thread(target=self._monitor)
1252 t.setDaemon(True)
1253 t.start()
1254
1255 def prepare(self , record):
1256 """
1257 Prepare a record for handling.
1258
1259 This method just returns the passed-in record. You may want to
1260 override this method if you need to do any custom marshalling or
1261 manipulation of the record before passing it to the handlers.
1262 """
1263 return record
1264
1265 def handle(self, record):
1266 """
1267 Handle a record.
1268
1269 This just loops through the handlers offering them the record
1270 to handle.
1271 """
1272 record = self.prepare(record)
1273 for handler in self.handlers:
1274 handler.handle(record)
1275
1276 def _monitor(self):
1277 """
1278 Monitor the queue for records, and ask the handler
1279 to deal with them.
1280
1281 This method runs on a separate, internal thread.
1282 The thread will terminate if it sees a sentinel object in the queue.
1283 """
1284 q = self.queue
1285 has_task_done = hasattr(q, 'task_done')
1286 while not self._stop.isSet():
1287 try:
1288 record = self.dequeue(True)
1289 if record is self._sentinel:
1290 break
1291 self.handle(record)
1292 if has_task_done:
1293 q.task_done()
1294 except queue.Empty:
1295 pass
1296 # There might still be records in the queue.
1297 while True:
1298 try:
1299 record = self.dequeue(False)
1300 if record is self._sentinel:
1301 break
1302 self.handle(record)
1303 if has_task_done:
1304 q.task_done()
1305 except queue.Empty:
1306 break
1307
1308 def stop(self):
1309 """
1310 Stop the listener.
1311
1312 This asks the thread to terminate, and then waits for it to do so.
1313 Note that if you don't call this before your application exits, there
1314 may be some records still left on the queue, which won't be processed.
1315 """
1316 self._stop.set()
1317 self.queue.put_nowait(self._sentinel)
1318 self._thread.join()
1319 self._thread = None