0001 # Copyright 2001-2004 by Vinay Sajip. All Rights Reserved. 0002 # 0003 # Permission to use, copy, modify, and distribute this software and its 0004 # documentation for any purpose and without fee is hereby granted, 0005 # provided that the above copyright notice appear in all copies and that 0006 # both that copyright notice and this permission notice appear in 0007 # supporting documentation, and that the name of Vinay Sajip 0008 # not be used in advertising or publicity pertaining to distribution 0009 # of the software without specific, written prior permission. 0010 # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 0011 # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 0012 # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 0013 # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 0014 # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 0015 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 0016 0017 """ 0018 Additional handlers for the logging package for Python. The core package is 0019 based on PEP 282 and comments thereto in comp.lang.python, and influenced by 0020 Apache's log4j system. 0021 0022 Should work under Python versions >= 1.5.2, except that source line 0023 information is not available unless 'sys._getframe()' is. 0024 0025 Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved. 0026 0027 To use, simply 'import logging' and log away! 0028 """ 0029 0030 import sys, logging, socket, types, os, string, cPickle, struct, time, glob 0031 0032 # 0033 # Some constants... 0034 # 0035 0036 DEFAULT_TCP_LOGGING_PORT = 9020 0037 DEFAULT_UDP_LOGGING_PORT = 9021 0038 DEFAULT_HTTP_LOGGING_PORT = 9022 0039 DEFAULT_SOAP_LOGGING_PORT = 9023 0040 SYSLOG_UDP_PORT = 514 0041 0042 class BaseRotatingHandler(logging.FileHandler): 0043 """ 0044 Base class for handlers that rotate log files at a certain point. 0045 Not meant to be instantiated directly. Instead, use RotatingFileHandler 0046 or TimedRotatingFileHandler. 0047 """ 0048 def __init__(self, filename, mode): 0049 """ 0050 Use the specified filename for streamed logging 0051 """ 0052 logging.FileHandler.__init__(self, filename, mode) 0053 0054 def emit(self, record): 0055 """ 0056 Emit a record. 0057 0058 Output the record to the file, catering for rollover as described 0059 in doRollover(). 0060 """ 0061 try: 0062 if self.shouldRollover(record): 0063 self.doRollover() 0064 logging.FileHandler.emit(self, record) 0065 except: 0066 self.handleError(record) 0067 0068 class RotatingFileHandler(BaseRotatingHandler): 0069 """ 0070 Handler for logging to a set of files, which switches from one file 0071 to the next when the current file reaches a certain size. 0072 """ 0073 def __init__(self, filename, mode="a", maxBytes=0, backupCount=0): 0074 """ 0075 Open the specified file and use it as the stream for logging. 0076 0077 By default, the file grows indefinitely. You can specify particular 0078 values of maxBytes and backupCount to allow the file to rollover at 0079 a predetermined size. 0080 0081 Rollover occurs whenever the current log file is nearly maxBytes in 0082 length. If backupCount is >= 1, the system will successively create 0083 new files with the same pathname as the base file, but with extensions 0084 ".1", ".2" etc. appended to it. For example, with a backupCount of 5 0085 and a base file name of "app.log", you would get "app.log", 0086 "app.log.1", "app.log.2", ... through to "app.log.5". The file being 0087 written to is always "app.log" - when it gets filled up, it is closed 0088 and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc. 0089 exist, then they are renamed to "app.log.2", "app.log.3" etc. 0090 respectively. 0091 0092 If maxBytes is zero, rollover never occurs. 0093 """ 0094 self.mode = mode 0095 if maxBytes > 0: 0096 self.mode = "a" # doesn't make sense otherwise! 0097 BaseRotatingHandler.__init__(self, filename, self.mode) 0098 self.maxBytes = maxBytes 0099 self.backupCount = backupCount 0100 0101 def doRollover(self): 0102 """ 0103 Do a rollover, as described in __init__(). 0104 """ 0105 0106 self.stream.close() 0107 if self.backupCount > 0: 0108 for i in range(self.backupCount - 1, 0, -1): 0109 sfn = "%s.%d" % (self.baseFilename, i) 0110 dfn = "%s.%d" % (self.baseFilename, i + 1) 0111 if os.path.exists(sfn): 0112 #print "%s -> %s" % (sfn, dfn) 0113 if os.path.exists(dfn): 0114 os.remove(dfn) 0115 os.rename(sfn, dfn) 0116 dfn = self.baseFilename + ".1" 0117 if os.path.exists(dfn): 0118 os.remove(dfn) 0119 os.rename(self.baseFilename, dfn) 0120 #print "%s -> %s" % (self.baseFilename, dfn) 0121 self.stream = open(self.baseFilename, "w") 0122 0123 def shouldRollover(self, record): 0124 """ 0125 Determine if rollover should occur. 0126 0127 Basically, see if the supplied record would cause the file to exceed 0128 the size limit we have. 0129 """ 0130 if self.maxBytes > 0: # are we rolling over? 0131 msg = "%s\n" % self.format(record) 0132 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature 0133 if self.stream.tell() + len(msg) >= self.maxBytes: 0134 return 1 0135 return 0 0136 0137 class TimedRotatingFileHandler(BaseRotatingHandler): 0138 """ 0139 Handler for logging to a file, rotating the log file at certain timed 0140 intervals. 0141 0142 If backupCount is > 0, when rollover is done, no more than backupCount 0143 files are kept - the oldest ones are deleted. 0144 """ 0145 def __init__(self, filename, when='h', interval=1, backupCount=0): 0146 BaseRotatingHandler.__init__(self, filename, 'a') 0147 self.when = string.upper(when) 0148 self.backupCount = backupCount 0149 # Calculate the real rollover interval, which is just the number of 0150 # seconds between rollovers. Also set the filename suffix used when 0151 # a rollover occurs. Current 'when' events supported: 0152 # S - Seconds 0153 # M - Minutes 0154 # H - Hours 0155 # D - Days 0156 # midnight - roll over at midnight 0157 # W{0-6} - roll over on a certain day; 0 - Monday 0158 # 0159 # Case of the 'when' specifier is not important; lower or upper case 0160 # will work. 0161 currentTime = int(time.time()) 0162 if self.when == 'S': 0163 self.interval = 1 # one second 0164 self.suffix = "%Y-%m-%d_%H-%M-%S" 0165 elif self.when == 'M': 0166 self.interval = 60 # one minute 0167 self.suffix = "%Y-%m-%d_%H-%M" 0168 elif self.when == 'H': 0169 self.interval = 60 * 60 # one hour 0170 self.suffix = "%Y-%m-%d_%H" 0171 elif self.when == 'D' or self.when == 'MIDNIGHT': 0172 self.interval = 60 * 60 * 24 # one day 0173 self.suffix = "%Y-%m-%d" 0174 elif self.when.startswith('W'): 0175 self.interval = 60 * 60 * 24 * 7 # one week 0176 if len(self.when) != 2: 0177 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when) 0178 if self.when[1] < '0' or self.when[1] > '6': 0179 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when) 0180 self.dayOfWeek = int(self.when[1]) 0181 self.suffix = "%Y-%m-%d" 0182 else: 0183 raise ValueError("Invalid rollover interval specified: %s" % self.when) 0184 0185 self.interval = self.interval * interval # multiply by units requested 0186 self.rolloverAt = currentTime + self.interval 0187 0188 # If we are rolling over at midnight or weekly, then the interval is already known. 0189 # What we need to figure out is WHEN the next interval is. In other words, 0190 # if you are rolling over at midnight, then your base interval is 1 day, 0191 # but you want to start that one day clock at midnight, not now. So, we 0192 # have to fudge the rolloverAt value in order to trigger the first rollover 0193 # at the right time. After that, the regular interval will take care of 0194 # the rest. Note that this code doesn't care about leap seconds. :) 0195 if self.when == 'MIDNIGHT' or self.when.startswith('W'): 0196 # This could be done with less code, but I wanted it to be clear 0197 t = time.localtime(currentTime) 0198 currentHour = t[3] 0199 currentMinute = t[4] 0200 currentSecond = t[5] 0201 # r is the number of seconds left between now and midnight 0202 r = (24 - currentHour) * 60 * 60 # number of hours in seconds 0203 r = r + (59 - currentMinute) * 60 # plus the number of minutes (in secs) 0204 r = r + (59 - currentSecond) # plus the number of seconds 0205 self.rolloverAt = currentTime + r 0206 # If we are rolling over on a certain day, add in the number of days until 0207 # the next rollover, but offset by 1 since we just calculated the time 0208 # until the next day starts. There are three cases: 0209 # Case 1) The day to rollover is today; in this case, do nothing 0210 # Case 2) The day to rollover is further in the interval (i.e., today is 0211 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to 0212 # next rollover is simply 6 - 2 - 1, or 3. 0213 # Case 3) The day to rollover is behind us in the interval (i.e., today 0214 # is day 5 (Saturday) and rollover is on day 3 (Thursday). 0215 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the 0216 # number of days left in the current week (1) plus the number 0217 # of days in the next week until the rollover day (3). 0218 if when.startswith('W'): 0219 day = t[6] # 0 is Monday 0220 if day > self.dayOfWeek: 0221 daysToWait = (day - self.dayOfWeek) - 1 0222 self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24)) 0223 if day < self.dayOfWeek: 0224 daysToWait = (6 - self.dayOfWeek) + day 0225 self.rolloverAt = self.rolloverAt + (daysToWait * (60 * 60 * 24)) 0226 0227 #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime) 0228 0229 def shouldRollover(self, record): 0230 """ 0231 Determine if rollover should occur 0232 0233 record is not used, as we are just comparing times, but it is needed so 0234 the method siguratures are the same 0235 """ 0236 t = int(time.time()) 0237 if t >= self.rolloverAt: 0238 return 1 0239 #print "No need to rollover: %d, %d" % (t, self.rolloverAt) 0240 return 0 0241 0242 def doRollover(self): 0243 """ 0244 do a rollover; in this case, a date/time stamp is appended to the filename 0245 when the rollover happens. However, you want the file to be named for the 0246 start of the interval, not the current time. If there is a backup count, 0247 then we have to get a list of matching filenames, sort them and remove 0248 the one with the oldest suffix. 0249 """ 0250 self.stream.close() 0251 # get the time that this sequence started at and make it a TimeTuple 0252 t = self.rolloverAt - self.interval 0253 timeTuple = time.localtime(t) 0254 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple) 0255 if os.path.exists(dfn): 0256 os.remove(dfn) 0257 os.rename(self.baseFilename, dfn) 0258 if self.backupCount > 0: 0259 # find the oldest log file and delete it 0260 s = glob.glob(self.baseFilename + ".20*") 0261 if len(s) > self.backupCount: 0262 s.sort() 0263 os.remove(s[0]) 0264 #print "%s -> %s" % (self.baseFilename, dfn) 0265 self.stream = open(self.baseFilename, "w") 0266 self.rolloverAt = int(time.time()) + self.interval 0267 0268 class SocketHandler(logging.Handler): 0269 """ 0270 A handler class which writes logging records, in pickle format, to 0271 a streaming socket. The socket is kept open across logging calls. 0272 If the peer resets it, an attempt is made to reconnect on the next call. 0273 The pickle which is sent is that of the LogRecord's attribute dictionary 0274 (__dict__), so that the receiver does not need to have the logging module 0275 installed in order to process the logging event. 0276 0277 To unpickle the record at the receiving end into a LogRecord, use the 0278 makeLogRecord function. 0279 """ 0280 0281 def __init__(self, host, port): 0282 """ 0283 Initializes the handler with a specific host address and port. 0284 0285 The attribute 'closeOnError' is set to 1 - which means that if 0286 a socket error occurs, the socket is silently closed and then 0287 reopened on the next logging call. 0288 """ 0289 logging.Handler.__init__(self) 0290 self.host = host 0291 self.port = port 0292 self.sock = None 0293 self.closeOnError = 0 0294 self.retryTime = None 0295 # 0296 # Exponential backoff parameters. 0297 # 0298 self.retryStart = 1.0 0299 self.retryMax = 30.0 0300 self.retryFactor = 2.0 0301 0302 def makeSocket(self): 0303 """ 0304 A factory method which allows subclasses to define the precise 0305 type of socket they want. 0306 """ 0307 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 0308 s.connect((self.host, self.port)) 0309 return s 0310 0311 def createSocket(self): 0312 """ 0313 Try to create a socket, using an exponential backoff with 0314 a max retry time. Thanks to Robert Olson for the original patch 0315 (SF #815911) which has been slightly refactored. 0316 """ 0317 now = time.time() 0318 # Either retryTime is None, in which case this 0319 # is the first time back after a disconnect, or 0320 # we've waited long enough. 0321 if self.retryTime is None: 0322 attempt = 1 0323 else: 0324 attempt = (now >= self.retryTime) 0325 if attempt: 0326 try: 0327 self.sock = self.makeSocket() 0328 self.retryTime = None # next time, no delay before trying 0329 except: 0330 #Creation failed, so set the retry time and return. 0331 if self.retryTime is None: 0332 self.retryPeriod = self.retryStart 0333 else: 0334 self.retryPeriod = self.retryPeriod * self.retryFactor 0335 if self.retryPeriod > self.retryMax: 0336 self.retryPeriod = self.retryMax 0337 self.retryTime = now + self.retryPeriod 0338 0339 def send(self, s): 0340 """ 0341 Send a pickled string to the socket. 0342 0343 This function allows for partial sends which can happen when the 0344 network is busy. 0345 """ 0346 if self.sock is None: 0347 self.createSocket() 0348 #self.sock can be None either because we haven't reached the retry 0349 #time yet, or because we have reached the retry time and retried, 0350 #but are still unable to connect. 0351 if self.sock: 0352 try: 0353 if hasattr(self.sock, "sendall"): 0354 self.sock.sendall(s) 0355 else: 0356 sentsofar = 0 0357 left = len(s) 0358 while left > 0: 0359 sent = self.sock.send(s[sentsofar:]) 0360 sentsofar = sentsofar + sent 0361 left = left - sent 0362 except socket.error: 0363 self.sock.close() 0364 self.sock = None # so we can call createSocket next time 0365 0366 def makePickle(self, record): 0367 """ 0368 Pickles the record in binary format with a length prefix, and 0369 returns it ready for transmission across the socket. 0370 """ 0371 ei = record.exc_info 0372 if ei: 0373 dummy = self.format(record) # just to get traceback text into record.exc_text 0374 record.exc_info = None # to avoid Unpickleable error 0375 s = cPickle.dumps(record.__dict__, 1) 0376 if ei: 0377 record.exc_info = ei # for next handler 0378 slen = struct.pack(">L", len(s)) 0379 return slen + s 0380 0381 def handleError(self, record): 0382 """ 0383 Handle an error during logging. 0384 0385 An error has occurred during logging. Most likely cause - 0386 connection lost. Close the socket so that we can retry on the 0387 next event. 0388 """ 0389 if self.closeOnError and self.sock: 0390 self.sock.close() 0391 self.sock = None #try to reconnect next time 0392 else: 0393 logging.Handler.handleError(self, record) 0394 0395 def emit(self, record): 0396 """ 0397 Emit a record. 0398 0399 Pickles the record and writes it to the socket in binary format. 0400 If there is an error with the socket, silently drop the packet. 0401 If there was a problem with the socket, re-establishes the 0402 socket. 0403 """ 0404 try: 0405 s = self.makePickle(record) 0406 self.send(s) 0407 except: 0408 self.handleError(record) 0409 0410 def close(self): 0411 """ 0412 Closes the socket. 0413 """ 0414 if self.sock: 0415 self.sock.close() 0416 self.sock = None 0417 logging.Handler.close(self) 0418 0419 class DatagramHandler(SocketHandler): 0420 """ 0421 A handler class which writes logging records, in pickle format, to 0422 a datagram socket. The pickle which is sent is that of the LogRecord's 0423 attribute dictionary (__dict__), so that the receiver does not need to 0424 have the logging module installed in order to process the logging event. 0425 0426 To unpickle the record at the receiving end into a LogRecord, use the 0427 makeLogRecord function. 0428 0429 """ 0430 def __init__(self, host, port): 0431 """ 0432 Initializes the handler with a specific host address and port. 0433 """ 0434 SocketHandler.__init__(self, host, port) 0435 self.closeOnError = 0 0436 0437 def makeSocket(self): 0438 """ 0439 The factory method of SocketHandler is here overridden to create 0440 a UDP socket (SOCK_DGRAM). 0441 """ 0442 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 0443 return s 0444 0445 def send(self, s): 0446 """ 0447 Send a pickled string to a socket. 0448 0449 This function no longer allows for partial sends which can happen 0450 when the network is busy - UDP does not guarantee delivery and 0451 can deliver packets out of sequence. 0452 """ 0453 if self.sock is None: 0454 self.createSocket() 0455 self.sock.sendto(s, (self.host, self.port)) 0456 0457 class SysLogHandler(logging.Handler): 0458 """ 0459 A handler class which sends formatted logging records to a syslog 0460 server. Based on Sam Rushing's syslog module: 0461 http://www.nightmare.com/squirl/python-ext/misc/syslog.py 0462 Contributed by Nicolas Untz (after which minor refactoring changes 0463 have been made). 0464 """ 0465 0466 # from <linux/sys/syslog.h>: 0467 # ====================================================================== 0468 # priorities/facilities are encoded into a single 32-bit quantity, where 0469 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the 0470 # facility (0-big number). Both the priorities and the facilities map 0471 # roughly one-to-one to strings in the syslogd(8) source code. This 0472 # mapping is included in this file. 0473 # 0474 # priorities (these are ordered) 0475 0476 LOG_EMERG = 0 # system is unusable 0477 LOG_ALERT = 1 # action must be taken immediately 0478 LOG_CRIT = 2 # critical conditions 0479 LOG_ERR = 3 # error conditions 0480 LOG_WARNING = 4 # warning conditions 0481 LOG_NOTICE = 5 # normal but significant condition 0482 LOG_INFO = 6 # informational 0483 LOG_DEBUG = 7 # debug-level messages 0484 0485 # facility codes 0486 LOG_KERN = 0 # kernel messages 0487 LOG_USER = 1 # random user-level messages 0488 LOG_MAIL = 2 # mail system 0489 LOG_DAEMON = 3 # system daemons 0490 LOG_AUTH = 4 # security/authorization messages 0491 LOG_SYSLOG = 5 # messages generated internally by syslogd 0492 LOG_LPR = 6 # line printer subsystem 0493 LOG_NEWS = 7 # network news subsystem 0494 LOG_UUCP = 8 # UUCP subsystem 0495 LOG_CRON = 9 # clock daemon 0496 LOG_AUTHPRIV = 10 # security/authorization messages (private) 0497 0498 # other codes through 15 reserved for system use 0499 LOG_LOCAL0 = 16 # reserved for local use 0500 LOG_LOCAL1 = 17 # reserved for local use 0501 LOG_LOCAL2 = 18 # reserved for local use 0502 LOG_LOCAL3 = 19 # reserved for local use 0503 LOG_LOCAL4 = 20 # reserved for local use 0504 LOG_LOCAL5 = 21 # reserved for local use 0505 LOG_LOCAL6 = 22 # reserved for local use 0506 LOG_LOCAL7 = 23 # reserved for local use 0507 0508 priority_names = { 0509 "alert": LOG_ALERT, 0510 "crit": LOG_CRIT, 0511 "critical": LOG_CRIT, 0512 "debug": LOG_DEBUG, 0513 "emerg": LOG_EMERG, 0514 "err": LOG_ERR, 0515 "error": LOG_ERR, # DEPRECATED 0516 "info": LOG_INFO, 0517 "notice": LOG_NOTICE, 0518 "panic": LOG_EMERG, # DEPRECATED 0519 "warn": LOG_WARNING, # DEPRECATED 0520 "warning": LOG_WARNING, 0521 } 0522 0523 facility_names = { 0524 "auth": LOG_AUTH, 0525 "authpriv": LOG_AUTHPRIV, 0526 "cron": LOG_CRON, 0527 "daemon": LOG_DAEMON, 0528 "kern": LOG_KERN, 0529 "lpr": LOG_LPR, 0530 "mail": LOG_MAIL, 0531 "news": LOG_NEWS, 0532 "security": LOG_AUTH, # DEPRECATED 0533 "syslog": LOG_SYSLOG, 0534 "user": LOG_USER, 0535 "uucp": LOG_UUCP, 0536 "local0": LOG_LOCAL0, 0537 "local1": LOG_LOCAL1, 0538 "local2": LOG_LOCAL2, 0539 "local3": LOG_LOCAL3, 0540 "local4": LOG_LOCAL4, 0541 "local5": LOG_LOCAL5, 0542 "local6": LOG_LOCAL6, 0543 "local7": LOG_LOCAL7, 0544 } 0545 0546 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER): 0547 """ 0548 Initialize a handler. 0549 0550 If address is specified as a string, UNIX socket is used. 0551 If facility is not specified, LOG_USER is used. 0552 """ 0553 logging.Handler.__init__(self) 0554 0555 self.address = address 0556 self.facility = facility 0557 if type(address) == types.StringType: 0558 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) 0559 # syslog may require either DGRAM or STREAM sockets 0560 try: 0561 self.socket.connect(address) 0562 except socket.error: 0563 self.socket.close() 0564 self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 0565 self.socket.connect(address) 0566 self.unixsocket = 1 0567 else: 0568 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 0569 self.unixsocket = 0 0570 0571 self.formatter = None 0572 0573 # curious: when talking to the unix-domain '/dev/log' socket, a 0574 # zero-terminator seems to be required. this string is placed 0575 # into a class variable so that it can be overridden if 0576 # necessary. 0577 log_format_string = '<%d>%s\000' 0578 0579 def encodePriority (self, facility, priority): 0580 """ 0581 Encode the facility and priority. You can pass in strings or 0582 integers - if strings are passed, the facility_names and 0583 priority_names mapping dictionaries are used to convert them to 0584 integers. 0585 """ 0586 if type(facility) == types.StringType: 0587 facility = self.facility_names[facility] 0588 if type(priority) == types.StringType: 0589 priority = self.priority_names[priority] 0590 return (facility << 3) | priority 0591 0592 def close (self): 0593 """ 0594 Closes the socket. 0595 """ 0596 if self.unixsocket: 0597 self.socket.close() 0598 logging.Handler.close(self) 0599 0600 def emit(self, record): 0601 """ 0602 Emit a record. 0603 0604 The record is formatted, and then sent to the syslog server. If 0605 exception information is present, it is NOT sent to the server. 0606 """ 0607 msg = self.format(record) 0608 """ 0609 We need to convert record level to lowercase, maybe this will 0610 change in the future. 0611 """ 0612 msg = self.log_format_string % ( 0613 self.encodePriority(self.facility, 0614 string.lower(record.levelname)), 0615 msg) 0616 try: 0617 if self.unixsocket: 0618 self.socket.send(msg) 0619 else: 0620 self.socket.sendto(msg, self.address) 0621 except: 0622 self.handleError(record) 0623 0624 class SMTPHandler(logging.Handler): 0625 """ 0626 A handler class which sends an SMTP email for each logging event. 0627 """ 0628 def __init__(self, mailhost, fromaddr, toaddrs, subject): 0629 """ 0630 Initialize the handler. 0631 0632 Initialize the instance with the from and to addresses and subject 0633 line of the email. To specify a non-standard SMTP port, use the 0634 (host, port) tuple format for the mailhost argument. 0635 """ 0636 logging.Handler.__init__(self) 0637 if type(mailhost) == types.TupleType: 0638 host, port = mailhost 0639 self.mailhost = host 0640 self.mailport = port 0641 else: 0642 self.mailhost = mailhost 0643 self.mailport = None 0644 self.fromaddr = fromaddr 0645 if type(toaddrs) == types.StringType: 0646 toaddrs = [toaddrs] 0647 self.toaddrs = toaddrs 0648 self.subject = subject 0649 0650 def getSubject(self, record): 0651 """ 0652 Determine the subject for the email. 0653 0654 If you want to specify a subject line which is record-dependent, 0655 override this method. 0656 """ 0657 return self.subject 0658 0659 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 0660 0661 monthname = [None, 0662 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 0663 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 0664 0665 def date_time(self): 0666 """ 0667 Return the current date and time formatted for a MIME header. 0668 Needed for Python 1.5.2 (no email package available) 0669 """ 0670 year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time()) 0671 s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( 0672 self.weekdayname[wd], 0673 day, self.monthname[month], year, 0674 hh, mm, ss) 0675 return s 0676 0677 def emit(self, record): 0678 """ 0679 Emit a record. 0680 0681 Format the record and send it to the specified addressees. 0682 """ 0683 try: 0684 import smtplib 0685 try: 0686 from email.Utils import formatdate 0687 except: 0688 formatdate = self.date_time 0689 port = self.mailport 0690 if not port: 0691 port = smtplib.SMTP_PORT 0692 smtp = smtplib.SMTP(self.mailhost, port) 0693 msg = self.format(record) 0694 msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( 0695 self.fromaddr, 0696 string.join(self.toaddrs, ","), 0697 self.getSubject(record), 0698 formatdate(), msg) 0699 smtp.sendmail(self.fromaddr, self.toaddrs, msg) 0700 smtp.quit() 0701 except: 0702 self.handleError(record) 0703 0704 class NTEventLogHandler(logging.Handler): 0705 """ 0706 A handler class which sends events to the NT Event Log. Adds a 0707 registry entry for the specified application name. If no dllname is 0708 provided, win32service.pyd (which contains some basic message 0709 placeholders) is used. Note that use of these placeholders will make 0710 your event logs big, as the entire message source is held in the log. 0711 If you want slimmer logs, you have to pass in the name of your own DLL 0712 which contains the message definitions you want to use in the event log. 0713 """ 0714 def __init__(self, appname, dllname=None, logtype="Application"): 0715 logging.Handler.__init__(self) 0716 try: 0717 import win32evtlogutil, win32evtlog 0718 self.appname = appname 0719 self._welu = win32evtlogutil 0720 if not dllname: 0721 dllname = os.path.split(self._welu.__file__) 0722 dllname = os.path.split(dllname[0]) 0723 dllname = os.path.join(dllname[0], r'win32service.pyd') 0724 self.dllname = dllname 0725 self.logtype = logtype 0726 self._welu.AddSourceToRegistry(appname, dllname, logtype) 0727 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE 0728 self.typemap = { 0729 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE, 0730 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE, 0731 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE, 0732 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE, 0733 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, 0734 } 0735 except ImportError: 0736 print "The Python Win32 extensions for NT (service, event "\ 0737 "logging) appear not to be available." 0738 self._welu = None 0739 0740 def getMessageID(self, record): 0741 """ 0742 Return the message ID for the event record. If you are using your 0743 own messages, you could do this by having the msg passed to the 0744 logger being an ID rather than a formatting string. Then, in here, 0745 you could use a dictionary lookup to get the message ID. This 0746 version returns 1, which is the base message ID in win32service.pyd. 0747 """ 0748 return 1 0749 0750 def getEventCategory(self, record): 0751 """ 0752 Return the event category for the record. 0753 0754 Override this if you want to specify your own categories. This version 0755 returns 0. 0756 """ 0757 return 0 0758 0759 def getEventType(self, record): 0760 """ 0761 Return the event type for the record. 0762 0763 Override this if you want to specify your own types. This version does 0764 a mapping using the handler's typemap attribute, which is set up in 0765 __init__() to a dictionary which contains mappings for DEBUG, INFO, 0766 WARNING, ERROR and CRITICAL. If you are using your own levels you will 0767 either need to override this method or place a suitable dictionary in 0768 the handler's typemap attribute. 0769 """ 0770 return self.typemap.get(record.levelno, self.deftype) 0771 0772 def emit(self, record): 0773 """ 0774 Emit a record. 0775 0776 Determine the message ID, event category and event type. Then 0777 log the message in the NT event log. 0778 """ 0779 if self._welu: 0780 try: 0781 id = self.getMessageID(record) 0782 cat = self.getEventCategory(record) 0783 type = self.getEventType(record) 0784 msg = self.format(record) 0785 self._welu.ReportEvent(self.appname, id, cat, type, [msg]) 0786 except: 0787 self.handleError(record) 0788 0789 def close(self): 0790 """ 0791 Clean up this handler. 0792 0793 You can remove the application name from the registry as a 0794 source of event log entries. However, if you do this, you will 0795 not be able to see the events as you intended in the Event Log 0796 Viewer - it needs to be able to access the registry to get the 0797 DLL name. 0798 """ 0799 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype) 0800 logging.Handler.close(self) 0801 0802 class HTTPHandler(logging.Handler): 0803 """ 0804 A class which sends records to a Web server, using either GET or 0805 POST semantics. 0806 """ 0807 def __init__(self, host, url, method="GET"): 0808 """ 0809 Initialize the instance with the host, the request URL, and the method 0810 ("GET" or "POST") 0811 """ 0812 logging.Handler.__init__(self) 0813 method = string.upper(method) 0814 if method not in ["GET", "POST"]: 0815 raise ValueError, "method must be GET or POST" 0816 self.host = host 0817 self.url = url 0818 self.method = method 0819 0820 def mapLogRecord(self, record): 0821 """ 0822 Default implementation of mapping the log record into a dict 0823 that is sent as the CGI data. Overwrite in your class. 0824 Contributed by Franz Glasner. 0825 """ 0826 return record.__dict__ 0827 0828 def emit(self, record): 0829 """ 0830 Emit a record. 0831 0832 Send the record to the Web server as an URL-encoded dictionary 0833 """ 0834 try: 0835 import httplib, urllib 0836 h = httplib.HTTP(self.host) 0837 url = self.url 0838 data = urllib.urlencode(self.mapLogRecord(record)) 0839 if self.method == "GET": 0840 if (string.find(url, '?') >= 0): 0841 sep = '&' 0842 else: 0843 sep = '?' 0844 url = url + "%c%s" % (sep, data) 0845 h.putrequest(self.method, url) 0846 if self.method == "POST": 0847 h.putheader("Content-length", str(len(data))) 0848 h.endheaders() 0849 if self.method == "POST": 0850 h.send(data) 0851 h.getreply() #can't do anything with the result 0852 except: 0853 self.handleError(record) 0854 0855 class BufferingHandler(logging.Handler): 0856 """ 0857 A handler class which buffers logging records in memory. Whenever each 0858 record is added to the buffer, a check is made to see if the buffer should 0859 be flushed. If it should, then flush() is expected to do what's needed. 0860 """ 0861 def __init__(self, capacity): 0862 """ 0863 Initialize the handler with the buffer size. 0864 """ 0865 logging.Handler.__init__(self) 0866 self.capacity = capacity 0867 self.buffer = [] 0868 0869 def shouldFlush(self, record): 0870 """ 0871 Should the handler flush its buffer? 0872 0873 Returns true if the buffer is up to capacity. This method can be 0874 overridden to implement custom flushing strategies. 0875 """ 0876 return (len(self.buffer) >= self.capacity) 0877 0878 def emit(self, record): 0879 """ 0880 Emit a record. 0881 0882 Append the record. If shouldFlush() tells us to, call flush() to process 0883 the buffer. 0884 """ 0885 self.buffer.append(record) 0886 if self.shouldFlush(record): 0887 self.flush() 0888 0889 def flush(self): 0890 """ 0891 Override to implement custom flushing behaviour. 0892 0893 This version just zaps the buffer to empty. 0894 """ 0895 self.buffer = [] 0896 0897 def close(self): 0898 """ 0899 Close the handler. 0900 0901 This version just flushes and chains to the parent class' close(). 0902 """ 0903 self.flush() 0904 logging.Handler.close(self) 0905 0906 class MemoryHandler(BufferingHandler): 0907 """ 0908 A handler class which buffers logging records in memory, periodically 0909 flushing them to a target handler. Flushing occurs whenever the buffer 0910 is full, or when an event of a certain severity or greater is seen. 0911 """ 0912 def __init__(self, capacity, flushLevel=logging.ERROR, target=None): 0913 """ 0914 Initialize the handler with the buffer size, the level at which 0915 flushing should occur and an optional target. 0916 0917 Note that without a target being set either here or via setTarget(), 0918 a MemoryHandler is no use to anyone! 0919 """ 0920 BufferingHandler.__init__(self, capacity) 0921 self.flushLevel = flushLevel 0922 self.target = target 0923 0924 def shouldFlush(self, record): 0925 """ 0926 Check for buffer full or a record at the flushLevel or higher. 0927 """ 0928 return (len(self.buffer) >= self.capacity) or \ 0929 (record.levelno >= self.flushLevel) 0930 0931 def setTarget(self, target): 0932 """ 0933 Set the target handler for this handler. 0934 """ 0935 self.target = target 0936 0937 def flush(self): 0938 """ 0939 For a MemoryHandler, flushing means just sending the buffered 0940 records to the target, if there is one. Override if you want 0941 different behaviour. 0942 """ 0943 if self.target: 0944 for record in self.buffer: 0945 self.target.handle(record) 0946 self.buffer = [] 0947 0948 def close(self): 0949 """ 0950 Flush, set the target to None and lose the buffer. 0951 """ 0952 self.flush() 0953 self.target = None 0954 BufferingHandler.close(self) 0955
Generated by PyXR 0.9.4