blob: 8a813d3e375ef046da879642e989361e8df24a8b [file] [log] [blame]
Vinay Sajip4dc385b2013-04-22 09:58:51 +01001# Copyright 2001-2013 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
Vinay Sajip16f6a292012-02-23 19:40:33 +000019based on PEP 282 and comments thereto in comp.lang.python.
Guido van Rossum57102f82002-11-13 16:15:58 +000020
Vinay Sajip4dc385b2013-04-22 09:58:51 +010021Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000022
Vinay Sajipaecf36a2009-01-20 23:16:08 +000023To use, simply 'import logging.handlers' and log away!
Guido van Rossum57102f82002-11-13 16:15:58 +000024"""
25
Vinay Sajip885f6fd2012-04-24 23:15:10 +010026import errno, logging, socket, os, cPickle, struct, time, re
Vinay Sajip9098ee42010-03-12 06:01:21 +000027from stat import ST_DEV, ST_INO, ST_MTIME
Guido van Rossum57102f82002-11-13 16:15:58 +000028
Vinay Sajip4600f112005-03-13 09:56:36 +000029try:
30 import codecs
31except ImportError:
32 codecs = None
Vinay Sajip5ac65282009-10-21 20:22:14 +000033try:
34 unicode
35 _unicode = True
36except NameError:
37 _unicode = False
Vinay Sajip4600f112005-03-13 09:56:36 +000038
Guido van Rossum57102f82002-11-13 16:15:58 +000039#
40# Some constants...
41#
42
43DEFAULT_TCP_LOGGING_PORT = 9020
44DEFAULT_UDP_LOGGING_PORT = 9021
45DEFAULT_HTTP_LOGGING_PORT = 9022
46DEFAULT_SOAP_LOGGING_PORT = 9023
47SYSLOG_UDP_PORT = 514
Vinay Sajip1c77b7f2009-10-10 20:32:36 +000048SYSLOG_TCP_PORT = 514
Guido van Rossum57102f82002-11-13 16:15:58 +000049
Vinay Sajip4b4a63e2006-05-02 08:35:36 +000050_MIDNIGHT = 24 * 60 * 60 # number of seconds in a day
51
Vinay Sajip17c52d82004-07-03 11:48:34 +000052class BaseRotatingHandler(logging.FileHandler):
53 """
54 Base class for handlers that rotate log files at a certain point.
55 Not meant to be instantiated directly. Instead, use RotatingFileHandler
56 or TimedRotatingFileHandler.
57 """
Vinay Sajip92aa2f82008-01-24 12:37:33 +000058 def __init__(self, filename, mode, encoding=None, delay=0):
Vinay Sajip17c52d82004-07-03 11:48:34 +000059 """
60 Use the specified filename for streamed logging
61 """
Vinay Sajip4600f112005-03-13 09:56:36 +000062 if codecs is None:
63 encoding = None
Vinay Sajip92aa2f82008-01-24 12:37:33 +000064 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
Vinay Sajip4600f112005-03-13 09:56:36 +000065 self.mode = mode
66 self.encoding = encoding
Guido van Rossum57102f82002-11-13 16:15:58 +000067
Vinay Sajip17c52d82004-07-03 11:48:34 +000068 def emit(self, record):
69 """
70 Emit a record.
71
72 Output the record to the file, catering for rollover as described
73 in doRollover().
74 """
Vinay Sajip3970c112004-07-08 10:24:04 +000075 try:
76 if self.shouldRollover(record):
77 self.doRollover()
78 logging.FileHandler.emit(self, record)
Vinay Sajip85c19092005-10-31 13:14:19 +000079 except (KeyboardInterrupt, SystemExit):
80 raise
Vinay Sajip3970c112004-07-08 10:24:04 +000081 except:
82 self.handleError(record)
Vinay Sajip17c52d82004-07-03 11:48:34 +000083
84class RotatingFileHandler(BaseRotatingHandler):
85 """
86 Handler for logging to a set of files, which switches from one file
87 to the next when the current file reaches a certain size.
88 """
Vinay Sajip92aa2f82008-01-24 12:37:33 +000089 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0):
Guido van Rossum57102f82002-11-13 16:15:58 +000090 """
91 Open the specified file and use it as the stream for logging.
92
93 By default, the file grows indefinitely. You can specify particular
94 values of maxBytes and backupCount to allow the file to rollover at
95 a predetermined size.
96
97 Rollover occurs whenever the current log file is nearly maxBytes in
98 length. If backupCount is >= 1, the system will successively create
99 new files with the same pathname as the base file, but with extensions
100 ".1", ".2" etc. appended to it. For example, with a backupCount of 5
101 and a base file name of "app.log", you would get "app.log",
102 "app.log.1", "app.log.2", ... through to "app.log.5". The file being
103 written to is always "app.log" - when it gets filled up, it is closed
104 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
105 exist, then they are renamed to "app.log.2", "app.log.3" etc.
106 respectively.
107
108 If maxBytes is zero, rollover never occurs.
109 """
Vinay Sajipe8b1eaf2010-08-22 18:11:02 +0000110 # If rotation/rollover is wanted, it doesn't make sense to use another
111 # mode. If for example 'w' were specified, then if there were multiple
112 # runs of the calling application, the logs from previous runs would be
113 # lost if the 'w' is respected, because the log file would be truncated
114 # on each run.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000115 if maxBytes > 0:
Vinay Sajipe8b1eaf2010-08-22 18:11:02 +0000116 mode = 'a'
Vinay Sajip92aa2f82008-01-24 12:37:33 +0000117 BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
Guido van Rossum57102f82002-11-13 16:15:58 +0000118 self.maxBytes = maxBytes
119 self.backupCount = backupCount
Guido van Rossum57102f82002-11-13 16:15:58 +0000120
121 def doRollover(self):
122 """
123 Do a rollover, as described in __init__().
124 """
Vinay Sajipaecf36a2009-01-20 23:16:08 +0000125 if self.stream:
126 self.stream.close()
Vinay Sajipcdb83882011-01-21 23:35:57 +0000127 self.stream = None
Guido van Rossum57102f82002-11-13 16:15:58 +0000128 if self.backupCount > 0:
129 for i in range(self.backupCount - 1, 0, -1):
130 sfn = "%s.%d" % (self.baseFilename, i)
131 dfn = "%s.%d" % (self.baseFilename, i + 1)
132 if os.path.exists(sfn):
133 #print "%s -> %s" % (sfn, dfn)
134 if os.path.exists(dfn):
135 os.remove(dfn)
136 os.rename(sfn, dfn)
137 dfn = self.baseFilename + ".1"
138 if os.path.exists(dfn):
139 os.remove(dfn)
Vinay Sajip6d7e2962013-09-06 10:09:45 +0100140 # Issue 18940: A file may not have been created if delay is True.
141 if os.path.exists(self.baseFilename):
142 os.rename(self.baseFilename, dfn)
Guido van Rossum57102f82002-11-13 16:15:58 +0000143 #print "%s -> %s" % (self.baseFilename, dfn)
Vinay Sajipb3d8a062007-01-16 09:50:07 +0000144 self.stream = self._open()
Guido van Rossum57102f82002-11-13 16:15:58 +0000145
Vinay Sajip17c52d82004-07-03 11:48:34 +0000146 def shouldRollover(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000147 """
Vinay Sajip17c52d82004-07-03 11:48:34 +0000148 Determine if rollover should occur.
Guido van Rossum57102f82002-11-13 16:15:58 +0000149
Vinay Sajip17c52d82004-07-03 11:48:34 +0000150 Basically, see if the supplied record would cause the file to exceed
151 the size limit we have.
Guido van Rossum57102f82002-11-13 16:15:58 +0000152 """
Vinay Sajipaecf36a2009-01-20 23:16:08 +0000153 if self.stream is None: # delay was set...
154 self.stream = self._open()
Guido van Rossum57102f82002-11-13 16:15:58 +0000155 if self.maxBytes > 0: # are we rolling over?
156 msg = "%s\n" % self.format(record)
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000157 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
Guido van Rossum57102f82002-11-13 16:15:58 +0000158 if self.stream.tell() + len(msg) >= self.maxBytes:
Vinay Sajip17c52d82004-07-03 11:48:34 +0000159 return 1
160 return 0
Guido van Rossum57102f82002-11-13 16:15:58 +0000161
Vinay Sajip17c52d82004-07-03 11:48:34 +0000162class TimedRotatingFileHandler(BaseRotatingHandler):
163 """
164 Handler for logging to a file, rotating the log file at certain timed
165 intervals.
166
167 If backupCount is > 0, when rollover is done, no more than backupCount
168 files are kept - the oldest ones are deleted.
169 """
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000170 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
Vinay Sajip92aa2f82008-01-24 12:37:33 +0000171 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000172 self.when = when.upper()
Vinay Sajip17c52d82004-07-03 11:48:34 +0000173 self.backupCount = backupCount
Vinay Sajipa12da732008-05-20 15:34:36 +0000174 self.utc = utc
Vinay Sajip17c52d82004-07-03 11:48:34 +0000175 # Calculate the real rollover interval, which is just the number of
176 # seconds between rollovers. Also set the filename suffix used when
177 # a rollover occurs. Current 'when' events supported:
178 # S - Seconds
179 # M - Minutes
180 # H - Hours
181 # D - Days
182 # midnight - roll over at midnight
183 # W{0-6} - roll over on a certain day; 0 - Monday
184 #
185 # Case of the 'when' specifier is not important; lower or upper case
186 # will work.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000187 if self.when == 'S':
188 self.interval = 1 # one second
189 self.suffix = "%Y-%m-%d_%H-%M-%S"
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000190 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000191 elif self.when == 'M':
192 self.interval = 60 # one minute
193 self.suffix = "%Y-%m-%d_%H-%M"
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000194 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000195 elif self.when == 'H':
196 self.interval = 60 * 60 # one hour
197 self.suffix = "%Y-%m-%d_%H"
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000198 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000199 elif self.when == 'D' or self.when == 'MIDNIGHT':
200 self.interval = 60 * 60 * 24 # one day
201 self.suffix = "%Y-%m-%d"
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000202 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000203 elif self.when.startswith('W'):
204 self.interval = 60 * 60 * 24 * 7 # one week
205 if len(self.when) != 2:
206 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
207 if self.when[1] < '0' or self.when[1] > '6':
208 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
209 self.dayOfWeek = int(self.when[1])
210 self.suffix = "%Y-%m-%d"
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000211 self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
Vinay Sajip17c52d82004-07-03 11:48:34 +0000212 else:
213 raise ValueError("Invalid rollover interval specified: %s" % self.when)
214
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000215 self.extMatch = re.compile(self.extMatch)
Vinay Sajipe7d40662004-10-03 19:12:07 +0000216 self.interval = self.interval * interval # multiply by units requested
Vinay Sajip9098ee42010-03-12 06:01:21 +0000217 if os.path.exists(filename):
218 t = os.stat(filename)[ST_MTIME]
219 else:
220 t = int(time.time())
221 self.rolloverAt = self.computeRollover(t)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000222
Vinay Sajip91290b52009-06-11 09:53:35 +0000223 def computeRollover(self, currentTime):
224 """
225 Work out the rollover time based on the specified time.
226 """
227 result = currentTime + self.interval
Vinay Sajip17c52d82004-07-03 11:48:34 +0000228 # If we are rolling over at midnight or weekly, then the interval is already known.
229 # What we need to figure out is WHEN the next interval is. In other words,
230 # if you are rolling over at midnight, then your base interval is 1 day,
231 # but you want to start that one day clock at midnight, not now. So, we
232 # have to fudge the rolloverAt value in order to trigger the first rollover
233 # at the right time. After that, the regular interval will take care of
234 # the rest. Note that this code doesn't care about leap seconds. :)
235 if self.when == 'MIDNIGHT' or self.when.startswith('W'):
236 # This could be done with less code, but I wanted it to be clear
Vinay Sajip83da0342009-06-11 09:23:41 +0000237 if self.utc:
Vinay Sajipa12da732008-05-20 15:34:36 +0000238 t = time.gmtime(currentTime)
239 else:
240 t = time.localtime(currentTime)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000241 currentHour = t[3]
242 currentMinute = t[4]
243 currentSecond = t[5]
244 # r is the number of seconds left between now and midnight
Vinay Sajip4b4a63e2006-05-02 08:35:36 +0000245 r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
246 currentSecond)
Vinay Sajip91290b52009-06-11 09:53:35 +0000247 result = currentTime + r
Vinay Sajip17c52d82004-07-03 11:48:34 +0000248 # If we are rolling over on a certain day, add in the number of days until
249 # the next rollover, but offset by 1 since we just calculated the time
250 # until the next day starts. There are three cases:
251 # Case 1) The day to rollover is today; in this case, do nothing
252 # Case 2) The day to rollover is further in the interval (i.e., today is
253 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
254 # next rollover is simply 6 - 2 - 1, or 3.
255 # Case 3) The day to rollover is behind us in the interval (i.e., today
256 # is day 5 (Saturday) and rollover is on day 3 (Thursday).
257 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
258 # number of days left in the current week (1) plus the number
259 # of days in the next week until the rollover day (3).
Vinay Sajipae747dc2008-01-21 17:02:26 +0000260 # The calculations described in 2) and 3) above need to have a day added.
261 # This is because the above time calculation takes us to midnight on this
262 # day, i.e. the start of the next day.
Vinay Sajip83da0342009-06-11 09:23:41 +0000263 if self.when.startswith('W'):
Vinay Sajip17c52d82004-07-03 11:48:34 +0000264 day = t[6] # 0 is Monday
Vinay Sajipbababa32007-10-24 10:47:06 +0000265 if day != self.dayOfWeek:
266 if day < self.dayOfWeek:
Vinay Sajipae747dc2008-01-21 17:02:26 +0000267 daysToWait = self.dayOfWeek - day
Vinay Sajipbababa32007-10-24 10:47:06 +0000268 else:
Vinay Sajipae747dc2008-01-21 17:02:26 +0000269 daysToWait = 6 - day + self.dayOfWeek + 1
Vinay Sajip91290b52009-06-11 09:53:35 +0000270 newRolloverAt = result + (daysToWait * (60 * 60 * 24))
Vinay Sajip83da0342009-06-11 09:23:41 +0000271 if not self.utc:
Vinay Sajipa12da732008-05-20 15:34:36 +0000272 dstNow = t[-1]
273 dstAtRollover = time.localtime(newRolloverAt)[-1]
274 if dstNow != dstAtRollover:
275 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
Vinay Sajip9790e082012-03-13 12:06:35 +0000276 addend = -3600
Vinay Sajipa12da732008-05-20 15:34:36 +0000277 else: # DST bows out before next rollover, so we need to add an hour
Vinay Sajip9790e082012-03-13 12:06:35 +0000278 addend = 3600
279 newRolloverAt += addend
Vinay Sajip91290b52009-06-11 09:53:35 +0000280 result = newRolloverAt
281 return result
Vinay Sajip17c52d82004-07-03 11:48:34 +0000282
Vinay Sajip17c52d82004-07-03 11:48:34 +0000283 def shouldRollover(self, record):
284 """
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000285 Determine if rollover should occur.
Vinay Sajip17c52d82004-07-03 11:48:34 +0000286
287 record is not used, as we are just comparing times, but it is needed so
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000288 the method signatures are the same
Vinay Sajip17c52d82004-07-03 11:48:34 +0000289 """
290 t = int(time.time())
291 if t >= self.rolloverAt:
292 return 1
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000293 #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000294 return 0
295
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000296 def getFilesToDelete(self):
297 """
298 Determine the files to delete when rolling over.
299
300 More specific than the earlier method, which just used glob.glob().
301 """
302 dirName, baseName = os.path.split(self.baseFilename)
303 fileNames = os.listdir(dirName)
304 result = []
305 prefix = baseName + "."
306 plen = len(prefix)
307 for fileName in fileNames:
308 if fileName[:plen] == prefix:
309 suffix = fileName[plen:]
310 if self.extMatch.match(suffix):
Vinay Sajipa12da732008-05-20 15:34:36 +0000311 result.append(os.path.join(dirName, fileName))
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000312 result.sort()
313 if len(result) < self.backupCount:
314 result = []
315 else:
316 result = result[:len(result) - self.backupCount]
317 return result
318
Vinay Sajip17c52d82004-07-03 11:48:34 +0000319 def doRollover(self):
320 """
321 do a rollover; in this case, a date/time stamp is appended to the filename
322 when the rollover happens. However, you want the file to be named for the
323 start of the interval, not the current time. If there is a backup count,
324 then we have to get a list of matching filenames, sort them and remove
325 the one with the oldest suffix.
326 """
Vinay Sajipaecf36a2009-01-20 23:16:08 +0000327 if self.stream:
328 self.stream.close()
Vinay Sajipcdb83882011-01-21 23:35:57 +0000329 self.stream = None
Vinay Sajip17c52d82004-07-03 11:48:34 +0000330 # get the time that this sequence started at and make it a TimeTuple
Vinay Sajip9790e082012-03-13 12:06:35 +0000331 currentTime = int(time.time())
332 dstNow = time.localtime(currentTime)[-1]
Vinay Sajip17c52d82004-07-03 11:48:34 +0000333 t = self.rolloverAt - self.interval
Vinay Sajipa12da732008-05-20 15:34:36 +0000334 if self.utc:
335 timeTuple = time.gmtime(t)
336 else:
337 timeTuple = time.localtime(t)
Vinay Sajip9790e082012-03-13 12:06:35 +0000338 dstThen = timeTuple[-1]
339 if dstNow != dstThen:
340 if dstNow:
341 addend = 3600
342 else:
343 addend = -3600
344 timeTuple = time.localtime(t + addend)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000345 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
346 if os.path.exists(dfn):
347 os.remove(dfn)
Vinay Sajip6d7e2962013-09-06 10:09:45 +0100348 # Issue 18940: A file may not have been created if delay is True.
349 if os.path.exists(self.baseFilename):
350 os.rename(self.baseFilename, dfn)
Vinay Sajip17c52d82004-07-03 11:48:34 +0000351 if self.backupCount > 0:
352 # find the oldest log file and delete it
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000353 #s = glob.glob(self.baseFilename + ".20*")
354 #if len(s) > self.backupCount:
355 # s.sort()
356 # os.remove(s[0])
357 for s in self.getFilesToDelete():
358 os.remove(s)
Vinay Sajip5e9e9e12004-07-12 09:21:41 +0000359 #print "%s -> %s" % (self.baseFilename, dfn)
Vinay Sajipb3d8a062007-01-16 09:50:07 +0000360 self.stream = self._open()
Vinay Sajip91290b52009-06-11 09:53:35 +0000361 newRolloverAt = self.computeRollover(currentTime)
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000362 while newRolloverAt <= currentTime:
363 newRolloverAt = newRolloverAt + self.interval
364 #If DST changes and midnight or weekly rollover, adjust for this.
Vinay Sajipa12da732008-05-20 15:34:36 +0000365 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000366 dstAtRollover = time.localtime(newRolloverAt)[-1]
367 if dstNow != dstAtRollover:
368 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
Vinay Sajip9790e082012-03-13 12:06:35 +0000369 addend = -3600
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000370 else: # DST bows out before next rollover, so we need to add an hour
Vinay Sajip9790e082012-03-13 12:06:35 +0000371 addend = 3600
372 newRolloverAt += addend
Vinay Sajipe5aefa02008-04-02 21:09:27 +0000373 self.rolloverAt = newRolloverAt
Guido van Rossum57102f82002-11-13 16:15:58 +0000374
Vinay Sajip73306b02007-01-14 21:49:59 +0000375class WatchedFileHandler(logging.FileHandler):
376 """
377 A handler for logging to a file, which watches the file
378 to see if it has changed while in use. This can happen because of
379 usage of programs such as newsyslog and logrotate which perform
380 log file rotation. This handler, intended for use under Unix,
381 watches the file to see if it has changed since the last emit.
382 (A file has changed if its device or inode have changed.)
383 If it has changed, the old file stream is closed, and the file
384 opened to get a new stream.
385
386 This handler is not appropriate for use under Windows, because
387 under Windows open files cannot be moved or renamed - logging
388 opens the files with exclusive locks - and so there is no need
389 for such a handler. Furthermore, ST_INO is not supported under
390 Windows; stat always returns zero for this value.
391
392 This handler is based on a suggestion and patch by Chad J.
393 Schroeder.
394 """
Vinay Sajip92aa2f82008-01-24 12:37:33 +0000395 def __init__(self, filename, mode='a', encoding=None, delay=0):
396 logging.FileHandler.__init__(self, filename, mode, encoding, delay)
Vinay Sajip885f6fd2012-04-24 23:15:10 +0100397 self.dev, self.ino = -1, -1
398 self._statstream()
399
400 def _statstream(self):
401 if self.stream:
402 sres = os.fstat(self.stream.fileno())
403 self.dev, self.ino = sres[ST_DEV], sres[ST_INO]
Vinay Sajip73306b02007-01-14 21:49:59 +0000404
405 def emit(self, record):
406 """
407 Emit a record.
408
409 First check if the underlying file has changed, and if it
410 has, close the old stream and reopen the file to get the
411 current stream.
412 """
Vinay Sajip885f6fd2012-04-24 23:15:10 +0100413 # Reduce the chance of race conditions by stat'ing by path only
414 # once and then fstat'ing our new fd if we opened a new log stream.
415 # See issue #14632: Thanks to John Mulligan for the problem report
416 # and patch.
417 try:
418 # stat the file by path, checking for existence
419 sres = os.stat(self.baseFilename)
420 except OSError as err:
421 if err.errno == errno.ENOENT:
422 sres = None
423 else:
424 raise
425 # compare file system stat with that of our stream file handle
426 if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino:
427 if self.stream is not None:
428 # we have an open file handle, clean it up
429 self.stream.flush()
430 self.stream.close()
431 # open a new file handle and get new stat info from that fd
432 self.stream = self._open()
433 self._statstream()
Vinay Sajip73306b02007-01-14 21:49:59 +0000434 logging.FileHandler.emit(self, record)
435
Guido van Rossum57102f82002-11-13 16:15:58 +0000436class SocketHandler(logging.Handler):
437 """
438 A handler class which writes logging records, in pickle format, to
439 a streaming socket. The socket is kept open across logging calls.
440 If the peer resets it, an attempt is made to reconnect on the next call.
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000441 The pickle which is sent is that of the LogRecord's attribute dictionary
442 (__dict__), so that the receiver does not need to have the logging module
443 installed in order to process the logging event.
444
445 To unpickle the record at the receiving end into a LogRecord, use the
446 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000447 """
448
449 def __init__(self, host, port):
450 """
451 Initializes the handler with a specific host address and port.
452
453 The attribute 'closeOnError' is set to 1 - which means that if
454 a socket error occurs, the socket is silently closed and then
455 reopened on the next logging call.
456 """
457 logging.Handler.__init__(self)
458 self.host = host
459 self.port = port
460 self.sock = None
461 self.closeOnError = 0
Vinay Sajip48cfe382004-02-20 13:17:27 +0000462 self.retryTime = None
463 #
464 # Exponential backoff parameters.
465 #
466 self.retryStart = 1.0
467 self.retryMax = 30.0
468 self.retryFactor = 2.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000469
Vinay Sajipaa7b16a2007-04-09 16:16:10 +0000470 def makeSocket(self, timeout=1):
Guido van Rossum57102f82002-11-13 16:15:58 +0000471 """
472 A factory method which allows subclasses to define the precise
473 type of socket they want.
474 """
475 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Vinay Sajipaa7b16a2007-04-09 16:16:10 +0000476 if hasattr(s, 'settimeout'):
477 s.settimeout(timeout)
Guido van Rossum57102f82002-11-13 16:15:58 +0000478 s.connect((self.host, self.port))
479 return s
480
Vinay Sajip48cfe382004-02-20 13:17:27 +0000481 def createSocket(self):
482 """
483 Try to create a socket, using an exponential backoff with
484 a max retry time. Thanks to Robert Olson for the original patch
485 (SF #815911) which has been slightly refactored.
486 """
487 now = time.time()
488 # Either retryTime is None, in which case this
489 # is the first time back after a disconnect, or
490 # we've waited long enough.
491 if self.retryTime is None:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000492 attempt = 1
Vinay Sajip48cfe382004-02-20 13:17:27 +0000493 else:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000494 attempt = (now >= self.retryTime)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000495 if attempt:
496 try:
497 self.sock = self.makeSocket()
498 self.retryTime = None # next time, no delay before trying
Vinay Sajipc683a872007-01-08 18:50:32 +0000499 except socket.error:
Vinay Sajip48cfe382004-02-20 13:17:27 +0000500 #Creation failed, so set the retry time and return.
501 if self.retryTime is None:
502 self.retryPeriod = self.retryStart
503 else:
504 self.retryPeriod = self.retryPeriod * self.retryFactor
505 if self.retryPeriod > self.retryMax:
506 self.retryPeriod = self.retryMax
507 self.retryTime = now + self.retryPeriod
508
Guido van Rossum57102f82002-11-13 16:15:58 +0000509 def send(self, s):
510 """
511 Send a pickled string to the socket.
512
513 This function allows for partial sends which can happen when the
514 network is busy.
515 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000516 if self.sock is None:
517 self.createSocket()
518 #self.sock can be None either because we haven't reached the retry
519 #time yet, or because we have reached the retry time and retried,
520 #but are still unable to connect.
521 if self.sock:
522 try:
523 if hasattr(self.sock, "sendall"):
524 self.sock.sendall(s)
525 else:
526 sentsofar = 0
527 left = len(s)
528 while left > 0:
529 sent = self.sock.send(s[sentsofar:])
530 sentsofar = sentsofar + sent
531 left = left - sent
532 except socket.error:
533 self.sock.close()
534 self.sock = None # so we can call createSocket next time
Guido van Rossum57102f82002-11-13 16:15:58 +0000535
536 def makePickle(self, record):
537 """
538 Pickles the record in binary format with a length prefix, and
539 returns it ready for transmission across the socket.
540 """
Vinay Sajip48cfe382004-02-20 13:17:27 +0000541 ei = record.exc_info
542 if ei:
Vinay Sajip7ce9bda2012-03-29 20:11:20 +0100543 # just to get traceback text into record.exc_text ...
544 dummy = self.format(record)
Tim Peters4e0e1b62004-07-07 20:54:48 +0000545 record.exc_info = None # to avoid Unpickleable error
Vinay Sajip7ce9bda2012-03-29 20:11:20 +0100546 # See issue #14436: If msg or args are objects, they may not be
547 # available on the receiving end. So we convert the msg % args
548 # to a string, save it as msg and zap the args.
549 d = dict(record.__dict__)
550 d['msg'] = record.getMessage()
551 d['args'] = None
552 s = cPickle.dumps(d, 1)
Vinay Sajip48cfe382004-02-20 13:17:27 +0000553 if ei:
Tim Peters4e0e1b62004-07-07 20:54:48 +0000554 record.exc_info = ei # for next handler
Guido van Rossum57102f82002-11-13 16:15:58 +0000555 slen = struct.pack(">L", len(s))
556 return slen + s
557
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000558 def handleError(self, record):
Guido van Rossum57102f82002-11-13 16:15:58 +0000559 """
560 Handle an error during logging.
561
562 An error has occurred during logging. Most likely cause -
563 connection lost. Close the socket so that we can retry on the
564 next event.
565 """
566 if self.closeOnError and self.sock:
567 self.sock.close()
568 self.sock = None #try to reconnect next time
569 else:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000570 logging.Handler.handleError(self, record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000571
572 def emit(self, record):
573 """
574 Emit a record.
575
576 Pickles the record and writes it to the socket in binary format.
577 If there is an error with the socket, silently drop the packet.
578 If there was a problem with the socket, re-establishes the
579 socket.
580 """
581 try:
582 s = self.makePickle(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000583 self.send(s)
Vinay Sajip85c19092005-10-31 13:14:19 +0000584 except (KeyboardInterrupt, SystemExit):
585 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000586 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000587 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000588
589 def close(self):
590 """
591 Closes the socket.
592 """
Vinay Sajip50198222012-02-23 20:45:03 +0000593 self.acquire()
594 try:
Vinay Sajipd23845e2012-02-23 19:37:18 +0000595 if self.sock:
596 self.sock.close()
597 self.sock = None
Vinay Sajip50198222012-02-23 20:45:03 +0000598 finally:
599 self.release()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000600 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000601
602class DatagramHandler(SocketHandler):
603 """
604 A handler class which writes logging records, in pickle format, to
Raymond Hettinger6f3eaa62003-06-27 21:43:39 +0000605 a datagram socket. The pickle which is sent is that of the LogRecord's
606 attribute dictionary (__dict__), so that the receiver does not need to
607 have the logging module installed in order to process the logging event.
608
609 To unpickle the record at the receiving end into a LogRecord, use the
610 makeLogRecord function.
Guido van Rossum57102f82002-11-13 16:15:58 +0000611
612 """
613 def __init__(self, host, port):
614 """
615 Initializes the handler with a specific host address and port.
616 """
617 SocketHandler.__init__(self, host, port)
618 self.closeOnError = 0
619
620 def makeSocket(self):
621 """
622 The factory method of SocketHandler is here overridden to create
623 a UDP socket (SOCK_DGRAM).
624 """
625 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
626 return s
627
628 def send(self, s):
629 """
630 Send a pickled string to a socket.
631
632 This function no longer allows for partial sends which can happen
633 when the network is busy - UDP does not guarantee delivery and
634 can deliver packets out of sequence.
635 """
Vinay Sajipfb154172004-08-24 09:36:23 +0000636 if self.sock is None:
637 self.createSocket()
Guido van Rossum57102f82002-11-13 16:15:58 +0000638 self.sock.sendto(s, (self.host, self.port))
639
640class SysLogHandler(logging.Handler):
641 """
642 A handler class which sends formatted logging records to a syslog
643 server. Based on Sam Rushing's syslog module:
644 http://www.nightmare.com/squirl/python-ext/misc/syslog.py
645 Contributed by Nicolas Untz (after which minor refactoring changes
646 have been made).
647 """
648
649 # from <linux/sys/syslog.h>:
650 # ======================================================================
651 # priorities/facilities are encoded into a single 32-bit quantity, where
652 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the
653 # facility (0-big number). Both the priorities and the facilities map
654 # roughly one-to-one to strings in the syslogd(8) source code. This
655 # mapping is included in this file.
656 #
657 # priorities (these are ordered)
658
659 LOG_EMERG = 0 # system is unusable
660 LOG_ALERT = 1 # action must be taken immediately
661 LOG_CRIT = 2 # critical conditions
662 LOG_ERR = 3 # error conditions
663 LOG_WARNING = 4 # warning conditions
664 LOG_NOTICE = 5 # normal but significant condition
665 LOG_INFO = 6 # informational
666 LOG_DEBUG = 7 # debug-level messages
667
668 # facility codes
669 LOG_KERN = 0 # kernel messages
670 LOG_USER = 1 # random user-level messages
671 LOG_MAIL = 2 # mail system
672 LOG_DAEMON = 3 # system daemons
673 LOG_AUTH = 4 # security/authorization messages
674 LOG_SYSLOG = 5 # messages generated internally by syslogd
675 LOG_LPR = 6 # line printer subsystem
676 LOG_NEWS = 7 # network news subsystem
677 LOG_UUCP = 8 # UUCP subsystem
678 LOG_CRON = 9 # clock daemon
Vinay Sajipb0623d62010-03-24 14:31:21 +0000679 LOG_AUTHPRIV = 10 # security/authorization messages (private)
680 LOG_FTP = 11 # FTP daemon
Guido van Rossum57102f82002-11-13 16:15:58 +0000681
682 # other codes through 15 reserved for system use
683 LOG_LOCAL0 = 16 # reserved for local use
684 LOG_LOCAL1 = 17 # reserved for local use
685 LOG_LOCAL2 = 18 # reserved for local use
686 LOG_LOCAL3 = 19 # reserved for local use
687 LOG_LOCAL4 = 20 # reserved for local use
688 LOG_LOCAL5 = 21 # reserved for local use
689 LOG_LOCAL6 = 22 # reserved for local use
690 LOG_LOCAL7 = 23 # reserved for local use
691
692 priority_names = {
693 "alert": LOG_ALERT,
694 "crit": LOG_CRIT,
695 "critical": LOG_CRIT,
696 "debug": LOG_DEBUG,
697 "emerg": LOG_EMERG,
698 "err": LOG_ERR,
699 "error": LOG_ERR, # DEPRECATED
700 "info": LOG_INFO,
701 "notice": LOG_NOTICE,
702 "panic": LOG_EMERG, # DEPRECATED
703 "warn": LOG_WARNING, # DEPRECATED
704 "warning": LOG_WARNING,
705 }
706
707 facility_names = {
708 "auth": LOG_AUTH,
709 "authpriv": LOG_AUTHPRIV,
710 "cron": LOG_CRON,
711 "daemon": LOG_DAEMON,
Vinay Sajipb0623d62010-03-24 14:31:21 +0000712 "ftp": LOG_FTP,
Guido van Rossum57102f82002-11-13 16:15:58 +0000713 "kern": LOG_KERN,
714 "lpr": LOG_LPR,
715 "mail": LOG_MAIL,
716 "news": LOG_NEWS,
717 "security": LOG_AUTH, # DEPRECATED
718 "syslog": LOG_SYSLOG,
719 "user": LOG_USER,
720 "uucp": LOG_UUCP,
721 "local0": LOG_LOCAL0,
722 "local1": LOG_LOCAL1,
723 "local2": LOG_LOCAL2,
724 "local3": LOG_LOCAL3,
725 "local4": LOG_LOCAL4,
726 "local5": LOG_LOCAL5,
727 "local6": LOG_LOCAL6,
728 "local7": LOG_LOCAL7,
729 }
730
Vinay Sajipdc579362006-07-20 23:20:12 +0000731 #The map below appears to be trivially lowercasing the key. However,
732 #there's more to it than meets the eye - in some locales, lowercasing
733 #gives unexpected results. See SF #1524081: in the Turkish locale,
734 #"INFO".lower() != "info"
735 priority_map = {
736 "DEBUG" : "debug",
737 "INFO" : "info",
738 "WARNING" : "warning",
739 "ERROR" : "error",
740 "CRITICAL" : "critical"
741 }
742
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000743 def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100744 facility=LOG_USER, socktype=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000745 """
746 Initialize a handler.
747
Vinay Sajip754a5fb2007-05-25 07:05:59 +0000748 If address is specified as a string, a UNIX socket is used. To log to a
749 local syslogd, "SysLogHandler(address="/dev/log")" can be used.
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100750 If facility is not specified, LOG_USER is used. If socktype is
751 specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific
752 socket type will be used. For Unix sockets, you can also specify a
753 socktype of None, in which case socket.SOCK_DGRAM will be used, falling
754 back to socket.SOCK_STREAM.
Guido van Rossum57102f82002-11-13 16:15:58 +0000755 """
756 logging.Handler.__init__(self)
757
758 self.address = address
759 self.facility = facility
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000760 self.socktype = socktype
761
762 if isinstance(address, basestring):
Guido van Rossum57102f82002-11-13 16:15:58 +0000763 self.unixsocket = 1
Vinay Sajip5492e172006-12-11 14:07:16 +0000764 self._connect_unixsocket(address)
Guido van Rossum57102f82002-11-13 16:15:58 +0000765 else:
Guido van Rossum57102f82002-11-13 16:15:58 +0000766 self.unixsocket = 0
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100767 if socktype is None:
768 socktype = socket.SOCK_DGRAM
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000769 self.socket = socket.socket(socket.AF_INET, socktype)
770 if socktype == socket.SOCK_STREAM:
771 self.socket.connect(address)
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100772 self.socktype = socktype
Guido van Rossum57102f82002-11-13 16:15:58 +0000773 self.formatter = None
774
Vinay Sajipa1974c12005-01-13 08:23:56 +0000775 def _connect_unixsocket(self, address):
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100776 use_socktype = self.socktype
777 if use_socktype is None:
778 use_socktype = socket.SOCK_DGRAM
779 self.socket = socket.socket(socket.AF_UNIX, use_socktype)
Vinay Sajipa1974c12005-01-13 08:23:56 +0000780 try:
781 self.socket.connect(address)
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100782 # it worked, so set self.socktype to the used type
783 self.socktype = use_socktype
Vinay Sajipa1974c12005-01-13 08:23:56 +0000784 except socket.error:
785 self.socket.close()
Vinay Sajip4dc385b2013-04-22 09:58:51 +0100786 if self.socktype is not None:
787 # user didn't specify falling back, so fail
788 raise
789 use_socktype = socket.SOCK_STREAM
790 self.socket = socket.socket(socket.AF_UNIX, use_socktype)
791 try:
792 self.socket.connect(address)
793 # it worked, so set self.socktype to the used type
794 self.socktype = use_socktype
795 except socket.error:
796 self.socket.close()
797 raise
Vinay Sajipa1974c12005-01-13 08:23:56 +0000798
Guido van Rossum57102f82002-11-13 16:15:58 +0000799 # curious: when talking to the unix-domain '/dev/log' socket, a
800 # zero-terminator seems to be required. this string is placed
801 # into a class variable so that it can be overridden if
802 # necessary.
803 log_format_string = '<%d>%s\000'
804
Vinay Sajipdc579362006-07-20 23:20:12 +0000805 def encodePriority(self, facility, priority):
Guido van Rossum57102f82002-11-13 16:15:58 +0000806 """
807 Encode the facility and priority. You can pass in strings or
808 integers - if strings are passed, the facility_names and
809 priority_names mapping dictionaries are used to convert them to
810 integers.
811 """
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000812 if isinstance(facility, basestring):
Guido van Rossum57102f82002-11-13 16:15:58 +0000813 facility = self.facility_names[facility]
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000814 if isinstance(priority, basestring):
Guido van Rossum57102f82002-11-13 16:15:58 +0000815 priority = self.priority_names[priority]
816 return (facility << 3) | priority
817
818 def close (self):
819 """
820 Closes the socket.
821 """
Vinay Sajip50198222012-02-23 20:45:03 +0000822 self.acquire()
823 try:
Vinay Sajipd23845e2012-02-23 19:37:18 +0000824 if self.unixsocket:
825 self.socket.close()
Vinay Sajip50198222012-02-23 20:45:03 +0000826 finally:
827 self.release()
Vinay Sajip48cfe382004-02-20 13:17:27 +0000828 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +0000829
Vinay Sajipdc579362006-07-20 23:20:12 +0000830 def mapPriority(self, levelName):
831 """
832 Map a logging level name to a key in the priority_names map.
833 This is useful in two scenarios: when custom levels are being
834 used, and in the case where you can't do a straightforward
835 mapping by lowercasing the logging level name because of locale-
836 specific issues (see SF #1524081).
837 """
838 return self.priority_map.get(levelName, "warning")
839
Guido van Rossum57102f82002-11-13 16:15:58 +0000840 def emit(self, record):
841 """
842 Emit a record.
843
844 The record is formatted, and then sent to the syslog server. If
845 exception information is present, it is NOT sent to the server.
846 """
Vinay Sajipe900b492010-09-03 09:06:07 +0000847 msg = self.format(record) + '\000'
Guido van Rossum57102f82002-11-13 16:15:58 +0000848 """
849 We need to convert record level to lowercase, maybe this will
850 change in the future.
851 """
Vinay Sajipe900b492010-09-03 09:06:07 +0000852 prio = '<%d>' % self.encodePriority(self.facility,
853 self.mapPriority(record.levelname))
854 # Message is a string. Convert to bytes as required by RFC 5424
855 if type(msg) is unicode:
Vinay Sajip5ac65282009-10-21 20:22:14 +0000856 msg = msg.encode('utf-8')
Vinay Sajipe900b492010-09-03 09:06:07 +0000857 msg = prio + msg
Guido van Rossum57102f82002-11-13 16:15:58 +0000858 try:
859 if self.unixsocket:
Vinay Sajipa1974c12005-01-13 08:23:56 +0000860 try:
861 self.socket.send(msg)
862 except socket.error:
Vinay Sajipbb6b51c2013-05-16 22:47:47 +0100863 self.socket.close() # See issue 17981
Vinay Sajipa1974c12005-01-13 08:23:56 +0000864 self._connect_unixsocket(self.address)
865 self.socket.send(msg)
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000866 elif self.socktype == socket.SOCK_DGRAM:
Guido van Rossum57102f82002-11-13 16:15:58 +0000867 self.socket.sendto(msg, self.address)
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000868 else:
869 self.socket.sendall(msg)
Vinay Sajip85c19092005-10-31 13:14:19 +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 SMTPHandler(logging.Handler):
876 """
877 A handler class which sends an SMTP email for each logging event.
878 """
Vinay Sajip48305662009-12-06 17:57:11 +0000879 def __init__(self, mailhost, fromaddr, toaddrs, subject,
Vinay Sajipbd1094a2009-12-06 18:05:04 +0000880 credentials=None, secure=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000881 """
882 Initialize the handler.
883
884 Initialize the instance with the from and to addresses and subject
885 line of the email. To specify a non-standard SMTP port, use the
Vinay Sajip70c8e8b2007-05-01 10:20:03 +0000886 (host, port) tuple format for the mailhost argument. To specify
887 authentication credentials, supply a (username, password) tuple
Vinay Sajip48305662009-12-06 17:57:11 +0000888 for the credentials argument. To specify the use of a secure
Vinay Sajipbd1094a2009-12-06 18:05:04 +0000889 protocol (TLS), pass in a tuple for the secure argument. This will
890 only be used when authentication credentials are supplied. The tuple
891 will be either an empty tuple, or a single-value tuple with the name
892 of a keyfile, or a 2-value tuple with the names of the keyfile and
893 certificate file. (This tuple is passed to the `starttls` method).
Guido van Rossum57102f82002-11-13 16:15:58 +0000894 """
895 logging.Handler.__init__(self)
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000896 if isinstance(mailhost, tuple):
Vinay Sajip70c8e8b2007-05-01 10:20:03 +0000897 self.mailhost, self.mailport = mailhost
Guido van Rossum57102f82002-11-13 16:15:58 +0000898 else:
Vinay Sajip70c8e8b2007-05-01 10:20:03 +0000899 self.mailhost, self.mailport = mailhost, None
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000900 if isinstance(credentials, tuple):
Vinay Sajip70c8e8b2007-05-01 10:20:03 +0000901 self.username, self.password = credentials
902 else:
903 self.username = None
Guido van Rossum57102f82002-11-13 16:15:58 +0000904 self.fromaddr = fromaddr
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000905 if isinstance(toaddrs, basestring):
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000906 toaddrs = [toaddrs]
Guido van Rossum57102f82002-11-13 16:15:58 +0000907 self.toaddrs = toaddrs
908 self.subject = subject
Vinay Sajip48305662009-12-06 17:57:11 +0000909 self.secure = secure
Vinay Sajip50d6f542012-03-23 14:33:00 +0000910 self._timeout = 5.0
Guido van Rossum57102f82002-11-13 16:15:58 +0000911
912 def getSubject(self, record):
913 """
914 Determine the subject for the email.
915
916 If you want to specify a subject line which is record-dependent,
917 override this method.
918 """
919 return self.subject
920
921 def emit(self, record):
922 """
923 Emit a record.
924
925 Format the record and send it to the specified addressees.
926 """
927 try:
928 import smtplib
Vinay Sajip7ce5c832010-02-07 13:06:51 +0000929 from email.utils import formatdate
Guido van Rossum57102f82002-11-13 16:15:58 +0000930 port = self.mailport
931 if not port:
932 port = smtplib.SMTP_PORT
Vinay Sajip50d6f542012-03-23 14:33:00 +0000933 smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout)
Guido van Rossum57102f82002-11-13 16:15:58 +0000934 msg = self.format(record)
Neal Norwitzf297bd12003-04-23 03:49:43 +0000935 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 +0000936 self.fromaddr,
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000937 ",".join(self.toaddrs),
Neal Norwitzf297bd12003-04-23 03:49:43 +0000938 self.getSubject(record),
Martin v. Löwis318a12e2004-08-18 12:27:40 +0000939 formatdate(), msg)
Vinay Sajip70c8e8b2007-05-01 10:20:03 +0000940 if self.username:
Vinay Sajipbd1094a2009-12-06 18:05:04 +0000941 if self.secure is not None:
Vinay Sajip48305662009-12-06 17:57:11 +0000942 smtp.ehlo()
Vinay Sajipbd1094a2009-12-06 18:05:04 +0000943 smtp.starttls(*self.secure)
Vinay Sajip48305662009-12-06 17:57:11 +0000944 smtp.ehlo()
Vinay Sajip70c8e8b2007-05-01 10:20:03 +0000945 smtp.login(self.username, self.password)
Guido van Rossum57102f82002-11-13 16:15:58 +0000946 smtp.sendmail(self.fromaddr, self.toaddrs, msg)
947 smtp.quit()
Vinay Sajip245a5ab2005-10-31 14:27:01 +0000948 except (KeyboardInterrupt, SystemExit):
949 raise
Guido van Rossum57102f82002-11-13 16:15:58 +0000950 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000951 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +0000952
953class NTEventLogHandler(logging.Handler):
954 """
955 A handler class which sends events to the NT Event Log. Adds a
956 registry entry for the specified application name. If no dllname is
957 provided, win32service.pyd (which contains some basic message
958 placeholders) is used. Note that use of these placeholders will make
959 your event logs big, as the entire message source is held in the log.
960 If you want slimmer logs, you have to pass in the name of your own DLL
961 which contains the message definitions you want to use in the event log.
962 """
963 def __init__(self, appname, dllname=None, logtype="Application"):
964 logging.Handler.__init__(self)
965 try:
966 import win32evtlogutil, win32evtlog
967 self.appname = appname
968 self._welu = win32evtlogutil
969 if not dllname:
970 dllname = os.path.split(self._welu.__file__)
971 dllname = os.path.split(dllname[0])
972 dllname = os.path.join(dllname[0], r'win32service.pyd')
973 self.dllname = dllname
974 self.logtype = logtype
975 self._welu.AddSourceToRegistry(appname, dllname, logtype)
976 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE
977 self.typemap = {
978 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE,
979 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE,
Neal Norwitz6fa635d2003-02-18 14:20:07 +0000980 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE,
Guido van Rossum57102f82002-11-13 16:15:58 +0000981 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE,
982 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE,
983 }
984 except ImportError:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000985 print("The Python Win32 extensions for NT (service, event "\
986 "logging) appear not to be available.")
Guido van Rossum57102f82002-11-13 16:15:58 +0000987 self._welu = None
988
989 def getMessageID(self, record):
990 """
991 Return the message ID for the event record. If you are using your
992 own messages, you could do this by having the msg passed to the
993 logger being an ID rather than a formatting string. Then, in here,
994 you could use a dictionary lookup to get the message ID. This
995 version returns 1, which is the base message ID in win32service.pyd.
996 """
997 return 1
998
999 def getEventCategory(self, record):
1000 """
1001 Return the event category for the record.
1002
1003 Override this if you want to specify your own categories. This version
1004 returns 0.
1005 """
1006 return 0
1007
1008 def getEventType(self, record):
1009 """
1010 Return the event type for the record.
1011
1012 Override this if you want to specify your own types. This version does
1013 a mapping using the handler's typemap attribute, which is set up in
1014 __init__() to a dictionary which contains mappings for DEBUG, INFO,
Neal Norwitz6fa635d2003-02-18 14:20:07 +00001015 WARNING, ERROR and CRITICAL. If you are using your own levels you will
Guido van Rossum57102f82002-11-13 16:15:58 +00001016 either need to override this method or place a suitable dictionary in
1017 the handler's typemap attribute.
1018 """
1019 return self.typemap.get(record.levelno, self.deftype)
1020
1021 def emit(self, record):
1022 """
1023 Emit a record.
1024
1025 Determine the message ID, event category and event type. Then
1026 log the message in the NT event log.
1027 """
1028 if self._welu:
1029 try:
1030 id = self.getMessageID(record)
1031 cat = self.getEventCategory(record)
1032 type = self.getEventType(record)
1033 msg = self.format(record)
1034 self._welu.ReportEvent(self.appname, id, cat, type, [msg])
Vinay Sajip245a5ab2005-10-31 14:27:01 +00001035 except (KeyboardInterrupt, SystemExit):
1036 raise
Guido van Rossum57102f82002-11-13 16:15:58 +00001037 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +00001038 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +00001039
1040 def close(self):
1041 """
1042 Clean up this handler.
1043
1044 You can remove the application name from the registry as a
1045 source of event log entries. However, if you do this, you will
1046 not be able to see the events as you intended in the Event Log
1047 Viewer - it needs to be able to access the registry to get the
1048 DLL name.
1049 """
1050 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype)
Vinay Sajip48cfe382004-02-20 13:17:27 +00001051 logging.Handler.close(self)
Guido van Rossum57102f82002-11-13 16:15:58 +00001052
1053class HTTPHandler(logging.Handler):
1054 """
1055 A class which sends records to a Web server, using either GET or
1056 POST semantics.
1057 """
1058 def __init__(self, host, url, method="GET"):
1059 """
1060 Initialize the instance with the host, the request URL, and the method
1061 ("GET" or "POST")
1062 """
1063 logging.Handler.__init__(self)
Vinay Sajip1c77b7f2009-10-10 20:32:36 +00001064 method = method.upper()
Guido van Rossum57102f82002-11-13 16:15:58 +00001065 if method not in ["GET", "POST"]:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +00001066 raise ValueError("method must be GET or POST")
Guido van Rossum57102f82002-11-13 16:15:58 +00001067 self.host = host
1068 self.url = url
1069 self.method = method
1070
Neal Norwitzf297bd12003-04-23 03:49:43 +00001071 def mapLogRecord(self, record):
1072 """
1073 Default implementation of mapping the log record into a dict
Vinay Sajip48cfe382004-02-20 13:17:27 +00001074 that is sent as the CGI data. Overwrite in your class.
Neal Norwitzf297bd12003-04-23 03:49:43 +00001075 Contributed by Franz Glasner.
1076 """
1077 return record.__dict__
1078
Guido van Rossum57102f82002-11-13 16:15:58 +00001079 def emit(self, record):
1080 """
1081 Emit a record.
1082
Senthil Kumarana5ba05c2010-08-09 20:18:04 +00001083 Send the record to the Web server as a percent-encoded dictionary
Guido van Rossum57102f82002-11-13 16:15:58 +00001084 """
1085 try:
1086 import httplib, urllib
Vinay Sajipb7935062005-10-11 13:15:31 +00001087 host = self.host
1088 h = httplib.HTTP(host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001089 url = self.url
Neal Norwitzf297bd12003-04-23 03:49:43 +00001090 data = urllib.urlencode(self.mapLogRecord(record))
Guido van Rossum57102f82002-11-13 16:15:58 +00001091 if self.method == "GET":
Vinay Sajip1c77b7f2009-10-10 20:32:36 +00001092 if (url.find('?') >= 0):
Guido van Rossum57102f82002-11-13 16:15:58 +00001093 sep = '&'
1094 else:
1095 sep = '?'
1096 url = url + "%c%s" % (sep, data)
1097 h.putrequest(self.method, url)
Vinay Sajipb7935062005-10-11 13:15:31 +00001098 # support multiple hosts on one IP address...
1099 # need to strip optional :port from host, if present
Vinay Sajip1c77b7f2009-10-10 20:32:36 +00001100 i = host.find(":")
Vinay Sajipb7935062005-10-11 13:15:31 +00001101 if i >= 0:
1102 host = host[:i]
1103 h.putheader("Host", host)
Guido van Rossum57102f82002-11-13 16:15:58 +00001104 if self.method == "POST":
Vinay Sajipb7935062005-10-11 13:15:31 +00001105 h.putheader("Content-type",
1106 "application/x-www-form-urlencoded")
Guido van Rossum57102f82002-11-13 16:15:58 +00001107 h.putheader("Content-length", str(len(data)))
Kristján Valur Jónsson84040db2009-01-09 20:27:16 +00001108 h.endheaders(data if self.method == "POST" else None)
Guido van Rossum57102f82002-11-13 16:15:58 +00001109 h.getreply() #can't do anything with the result
Vinay Sajip245a5ab2005-10-31 14:27:01 +00001110 except (KeyboardInterrupt, SystemExit):
1111 raise
Guido van Rossum57102f82002-11-13 16:15:58 +00001112 except:
Neal Norwitz6fa635d2003-02-18 14:20:07 +00001113 self.handleError(record)
Guido van Rossum57102f82002-11-13 16:15:58 +00001114
1115class BufferingHandler(logging.Handler):
1116 """
1117 A handler class which buffers logging records in memory. Whenever each
1118 record is added to the buffer, a check is made to see if the buffer should
1119 be flushed. If it should, then flush() is expected to do what's needed.
1120 """
1121 def __init__(self, capacity):
1122 """
1123 Initialize the handler with the buffer size.
1124 """
1125 logging.Handler.__init__(self)
1126 self.capacity = capacity
1127 self.buffer = []
1128
1129 def shouldFlush(self, record):
1130 """
1131 Should the handler flush its buffer?
1132
1133 Returns true if the buffer is up to capacity. This method can be
1134 overridden to implement custom flushing strategies.
1135 """
1136 return (len(self.buffer) >= self.capacity)
1137
1138 def emit(self, record):
1139 """
1140 Emit a record.
1141
1142 Append the record. If shouldFlush() tells us to, call flush() to process
1143 the buffer.
1144 """
1145 self.buffer.append(record)
1146 if self.shouldFlush(record):
1147 self.flush()
1148
1149 def flush(self):
1150 """
1151 Override to implement custom flushing behaviour.
1152
1153 This version just zaps the buffer to empty.
1154 """
Vinay Sajip50198222012-02-23 20:45:03 +00001155 self.acquire()
1156 try:
Vinay Sajipd23845e2012-02-23 19:37:18 +00001157 self.buffer = []
Vinay Sajip50198222012-02-23 20:45:03 +00001158 finally:
1159 self.release()
Guido van Rossum57102f82002-11-13 16:15:58 +00001160
Vinay Sajipf42d95e2004-02-21 22:14:34 +00001161 def close(self):
1162 """
1163 Close the handler.
1164
1165 This version just flushes and chains to the parent class' close().
1166 """
1167 self.flush()
1168 logging.Handler.close(self)
1169
Guido van Rossum57102f82002-11-13 16:15:58 +00001170class MemoryHandler(BufferingHandler):
1171 """
1172 A handler class which buffers logging records in memory, periodically
1173 flushing them to a target handler. Flushing occurs whenever the buffer
1174 is full, or when an event of a certain severity or greater is seen.
1175 """
1176 def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
1177 """
1178 Initialize the handler with the buffer size, the level at which
1179 flushing should occur and an optional target.
1180
1181 Note that without a target being set either here or via setTarget(),
1182 a MemoryHandler is no use to anyone!
1183 """
1184 BufferingHandler.__init__(self, capacity)
1185 self.flushLevel = flushLevel
1186 self.target = target
1187
1188 def shouldFlush(self, record):
1189 """
1190 Check for buffer full or a record at the flushLevel or higher.
1191 """
1192 return (len(self.buffer) >= self.capacity) or \
1193 (record.levelno >= self.flushLevel)
1194
1195 def setTarget(self, target):
1196 """
1197 Set the target handler for this handler.
1198 """
1199 self.target = target
1200
1201 def flush(self):
1202 """
1203 For a MemoryHandler, flushing means just sending the buffered
1204 records to the target, if there is one. Override if you want
1205 different behaviour.
1206 """
Vinay Sajip50198222012-02-23 20:45:03 +00001207 self.acquire()
1208 try:
Vinay Sajipd23845e2012-02-23 19:37:18 +00001209 if self.target:
1210 for record in self.buffer:
1211 self.target.handle(record)
1212 self.buffer = []
Vinay Sajip50198222012-02-23 20:45:03 +00001213 finally:
1214 self.release()
Guido van Rossum57102f82002-11-13 16:15:58 +00001215
1216 def close(self):
1217 """
1218 Flush, set the target to None and lose the buffer.
1219 """
1220 self.flush()
Vinay Sajip50198222012-02-23 20:45:03 +00001221 self.acquire()
1222 try:
Vinay Sajipd23845e2012-02-23 19:37:18 +00001223 self.target = None
1224 BufferingHandler.close(self)
Vinay Sajip50198222012-02-23 20:45:03 +00001225 finally:
1226 self.release()