1 # Copyright 2001-2013 by Vinay Sajip. All Rights Reserved. 2 # 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. 16 17 """ 18 Additional handlers for the logging package for Python. The core package is 19 based on PEP 282 and comments thereto in comp.lang.python. 20 21 Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved. 22 23 To use, simply 'import logging.handlers' and log away! 24 """ 25 26 import errno, logging, socket, os, cPickle, struct, time, re 27 from stat import ST_DEV, ST_INO, ST_MTIME 28 29 try: 30 import codecs 31 except ImportError: 32 codecs = None 33 try: 34 unicode 35 _unicode = True 36 except NameError: 37 _unicode = False 38 39 # 40 # Some constants... 41 # 42 43 DEFAULT_TCP_LOGGING_PORT = 9020 44 DEFAULT_UDP_LOGGING_PORT = 9021 45 DEFAULT_HTTP_LOGGING_PORT = 9022 46 DEFAULT_SOAP_LOGGING_PORT = 9023 47 SYSLOG_UDP_PORT = 514 48 SYSLOG_TCP_PORT = 514 49 50 _MIDNIGHT = 24 * 60 * 60 # number of seconds in a day 51 52 class 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 """ 58 def __init__(self, filename, mode, encoding=None, delay=0): 59 """ 60 Use the specified filename for streamed logging 61 """ 62 if codecs is None: 63 encoding = None 64 logging.FileHandler.__init__(self, filename, mode, encoding, delay) 65 self.mode = mode 66 self.encoding = encoding 67 68 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 """ 75 try: 76 if self.shouldRollover(record): 77 self.doRollover() 78 logging.FileHandler.emit(self, record) 79 except (KeyboardInterrupt, SystemExit): 80 raise 81 except: 82 self.handleError(record) 83 84 class 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 """ 89 def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0): 90 """ 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 """ 110 # 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. 115 if maxBytes > 0: 116 mode = 'a' 117 BaseRotatingHandler.__init__(self, filename, mode, encoding, delay) 118 self.maxBytes = maxBytes 119 self.backupCount = backupCount 120 121 def doRollover(self): 122 """ 123 Do a rollover, as described in __init__(). 124 """ 125 if self.stream: 126 self.stream.close() 127 self.stream = None 128 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) 140 os.rename(self.baseFilename, dfn) 141 #print "%s -> %s" % (self.baseFilename, dfn) 142 self.stream = self._open() 143 144 def shouldRollover(self, record): 145 """ 146 Determine if rollover should occur. 147 148 Basically, see if the supplied record would cause the file to exceed 149 the size limit we have. 150 """ 151 if self.stream is None: # delay was set... 152 self.stream = self._open() 153 if self.maxBytes > 0: # are we rolling over? 154 msg = "%s\n" % self.format(record) 155 self.stream.seek(0, 2) #due to non-posix-compliant Windows feature 156 if self.stream.tell() + len(msg) >= self.maxBytes: 157 return 1 158 return 0 159 160 class TimedRotatingFileHandler(BaseRotatingHandler): 161 """ 162 Handler for logging to a file, rotating the log file at certain timed 163 intervals. 164 165 If backupCount is > 0, when rollover is done, no more than backupCount 166 files are kept - the oldest ones are deleted. 167 """ 168 def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False): 169 BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay) 170 self.when = when.upper() 171 self.backupCount = backupCount 172 self.utc = utc 173 # Calculate the real rollover interval, which is just the number of 174 # seconds between rollovers. Also set the filename suffix used when 175 # a rollover occurs. Current 'when' events supported: 176 # S - Seconds 177 # M - Minutes 178 # H - Hours 179 # D - Days 180 # midnight - roll over at midnight 181 # W{0-6} - roll over on a certain day; 0 - Monday 182 # 183 # Case of the 'when' specifier is not important; lower or upper case 184 # will work. 185 if self.when == 'S': 186 self.interval = 1 # one second 187 self.suffix = "%Y-%m-%d_%H-%M-%S" 188 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$" 189 elif self.when == 'M': 190 self.interval = 60 # one minute 191 self.suffix = "%Y-%m-%d_%H-%M" 192 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$" 193 elif self.when == 'H': 194 self.interval = 60 * 60 # one hour 195 self.suffix = "%Y-%m-%d_%H" 196 self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$" 197 elif self.when == 'D' or self.when == 'MIDNIGHT': 198 self.interval = 60 * 60 * 24 # one day 199 self.suffix = "%Y-%m-%d" 200 self.extMatch = r"^\d{4}-\d{2}-\d{2}$" 201 elif self.when.startswith('W'): 202 self.interval = 60 * 60 * 24 * 7 # one week 203 if len(self.when) != 2: 204 raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when) 205 if self.when[1] < '0' or self.when[1] > '6': 206 raise ValueError("Invalid day specified for weekly rollover: %s" % self.when) 207 self.dayOfWeek = int(self.when[1]) 208 self.suffix = "%Y-%m-%d" 209 self.extMatch = r"^\d{4}-\d{2}-\d{2}$" 210 else: 211 raise ValueError("Invalid rollover interval specified: %s" % self.when) 212 213 self.extMatch = re.compile(self.extMatch) 214 self.interval = self.interval * interval # multiply by units requested 215 if os.path.exists(filename): 216 t = os.stat(filename)[ST_MTIME] 217 else: 218 t = int(time.time()) 219 self.rolloverAt = self.computeRollover(t) 220 221 def computeRollover(self, currentTime): 222 """ 223 Work out the rollover time based on the specified time. 224 """ 225 result = currentTime + self.interval 226 # If we are rolling over at midnight or weekly, then the interval is already known. 227 # What we need to figure out is WHEN the next interval is. In other words, 228 # if you are rolling over at midnight, then your base interval is 1 day, 229 # but you want to start that one day clock at midnight, not now. So, we 230 # have to fudge the rolloverAt value in order to trigger the first rollover 231 # at the right time. After that, the regular interval will take care of 232 # the rest. Note that this code doesn't care about leap seconds. :) 233 if self.when == 'MIDNIGHT' or self.when.startswith('W'): 234 # This could be done with less code, but I wanted it to be clear 235 if self.utc: 236 t = time.gmtime(currentTime) 237 else: 238 t = time.localtime(currentTime) 239 currentHour = t[3] 240 currentMinute = t[4] 241 currentSecond = t[5] 242 # r is the number of seconds left between now and midnight 243 r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 + 244 currentSecond) 245 result = currentTime + r 246 # If we are rolling over on a certain day, add in the number of days until 247 # the next rollover, but offset by 1 since we just calculated the time 248 # until the next day starts. There are three cases: 249 # Case 1) The day to rollover is today; in this case, do nothing 250 # Case 2) The day to rollover is further in the interval (i.e., today is 251 # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to 252 # next rollover is simply 6 - 2 - 1, or 3. 253 # Case 3) The day to rollover is behind us in the interval (i.e., today 254 # is day 5 (Saturday) and rollover is on day 3 (Thursday). 255 # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the 256 # number of days left in the current week (1) plus the number 257 # of days in the next week until the rollover day (3). 258 # The calculations described in 2) and 3) above need to have a day added. 259 # This is because the above time calculation takes us to midnight on this 260 # day, i.e. the start of the next day. 261 if self.when.startswith('W'): 262 day = t[6] # 0 is Monday 263 if day != self.dayOfWeek: 264 if day < self.dayOfWeek: 265 daysToWait = self.dayOfWeek - day 266 else: 267 daysToWait = 6 - day + self.dayOfWeek + 1 268 newRolloverAt = result + (daysToWait * (60 * 60 * 24)) 269 if not self.utc: 270 dstNow = t[-1] 271 dstAtRollover = time.localtime(newRolloverAt)[-1] 272 if dstNow != dstAtRollover: 273 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour 274 addend = -3600 275 else: # DST bows out before next rollover, so we need to add an hour 276 addend = 3600 277 newRolloverAt += addend 278 result = newRolloverAt 279 return result 280 281 def shouldRollover(self, record): 282 """ 283 Determine if rollover should occur. 284 285 record is not used, as we are just comparing times, but it is needed so 286 the method signatures are the same 287 """ 288 t = int(time.time()) 289 if t >= self.rolloverAt: 290 return 1 291 #print "No need to rollover: %d, %d" % (t, self.rolloverAt) 292 return 0 293 294 def getFilesToDelete(self): 295 """ 296 Determine the files to delete when rolling over. 297 298 More specific than the earlier method, which just used glob.glob(). 299 """ 300 dirName, baseName = os.path.split(self.baseFilename) 301 fileNames = os.listdir(dirName) 302 result = [] 303 prefix = baseName + "." 304 plen = len(prefix) 305 for fileName in fileNames: 306 if fileName[:plen] == prefix: 307 suffix = fileName[plen:] 308 if self.extMatch.match(suffix): 309 result.append(os.path.join(dirName, fileName)) 310 result.sort() 311 if len(result) < self.backupCount: 312 result = [] 313 else: 314 result = result[:len(result) - self.backupCount] 315 return result 316 317 def doRollover(self): 318 """ 319 do a rollover; in this case, a date/time stamp is appended to the filename 320 when the rollover happens. However, you want the file to be named for the 321 start of the interval, not the current time. If there is a backup count, 322 then we have to get a list of matching filenames, sort them and remove 323 the one with the oldest suffix. 324 """ 325 if self.stream: 326 self.stream.close() 327 self.stream = None 328 # get the time that this sequence started at and make it a TimeTuple 329 currentTime = int(time.time()) 330 dstNow = time.localtime(currentTime)[-1] 331 t = self.rolloverAt - self.interval 332 if self.utc: 333 timeTuple = time.gmtime(t) 334 else: 335 timeTuple = time.localtime(t) 336 dstThen = timeTuple[-1] 337 if dstNow != dstThen: 338 if dstNow: 339 addend = 3600 340 else: 341 addend = -3600 342 timeTuple = time.localtime(t + addend) 343 dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple) 344 if os.path.exists(dfn): 345 os.remove(dfn) 346 os.rename(self.baseFilename, dfn) 347 if self.backupCount > 0: 348 # find the oldest log file and delete it 349 #s = glob.glob(self.baseFilename + ".20*") 350 #if len(s) > self.backupCount: 351 # s.sort() 352 # os.remove(s[0]) 353 for s in self.getFilesToDelete(): 354 os.remove(s) 355 #print "%s -> %s" % (self.baseFilename, dfn) 356 self.stream = self._open() 357 newRolloverAt = self.computeRollover(currentTime) 358 while newRolloverAt <= currentTime: 359 newRolloverAt = newRolloverAt + self.interval 360 #If DST changes and midnight or weekly rollover, adjust for this. 361 if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: 362 dstAtRollover = time.localtime(newRolloverAt)[-1] 363 if dstNow != dstAtRollover: 364 if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour 365 addend = -3600 366 else: # DST bows out before next rollover, so we need to add an hour 367 addend = 3600 368 newRolloverAt += addend 369 self.rolloverAt = newRolloverAt 370 371 class WatchedFileHandler(logging.FileHandler): 372 """ 373 A handler for logging to a file, which watches the file 374 to see if it has changed while in use. This can happen because of 375 usage of programs such as newsyslog and logrotate which perform 376 log file rotation. This handler, intended for use under Unix, 377 watches the file to see if it has changed since the last emit. 378 (A file has changed if its device or inode have changed.) 379 If it has changed, the old file stream is closed, and the file 380 opened to get a new stream. 381 382 This handler is not appropriate for use under Windows, because 383 under Windows open files cannot be moved or renamed - logging 384 opens the files with exclusive locks - and so there is no need 385 for such a handler. Furthermore, ST_INO is not supported under 386 Windows; stat always returns zero for this value. 387 388 This handler is based on a suggestion and patch by Chad J. 389 Schroeder. 390 """ 391 def __init__(self, filename, mode='a', encoding=None, delay=0): 392 logging.FileHandler.__init__(self, filename, mode, encoding, delay) 393 self.dev, self.ino = -1, -1 394 self._statstream() 395 396 def _statstream(self): 397 if self.stream: 398 sres = os.fstat(self.stream.fileno()) 399 self.dev, self.ino = sres[ST_DEV], sres[ST_INO] 400 401 def emit(self, record): 402 """ 403 Emit a record. 404 405 First check if the underlying file has changed, and if it 406 has, close the old stream and reopen the file to get the 407 current stream. 408 """ 409 # Reduce the chance of race conditions by stat'ing by path only 410 # once and then fstat'ing our new fd if we opened a new log stream. 411 # See issue #14632: Thanks to John Mulligan for the problem report 412 # and patch. 413 try: 414 # stat the file by path, checking for existence 415 sres = os.stat(self.baseFilename) 416 except OSError as err: 417 if err.errno == errno.ENOENT: 418 sres = None 419 else: 420 raise 421 # compare file system stat with that of our stream file handle 422 if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino: 423 if self.stream is not None: 424 # we have an open file handle, clean it up 425 self.stream.flush() 426 self.stream.close() 427 # open a new file handle and get new stat info from that fd 428 self.stream = self._open() 429 self._statstream() 430 logging.FileHandler.emit(self, record) 431 432 class SocketHandler(logging.Handler): 433 """ 434 A handler class which writes logging records, in pickle format, to 435 a streaming socket. The socket is kept open across logging calls. 436 If the peer resets it, an attempt is made to reconnect on the next call. 437 The pickle which is sent is that of the LogRecord's attribute dictionary 438 (__dict__), so that the receiver does not need to have the logging module 439 installed in order to process the logging event. 440 441 To unpickle the record at the receiving end into a LogRecord, use the 442 makeLogRecord function. 443 """ 444 445 def __init__(self, host, port): 446 """ 447 Initializes the handler with a specific host address and port. 448 449 The attribute 'closeOnError' is set to 1 - which means that if 450 a socket error occurs, the socket is silently closed and then 451 reopened on the next logging call. 452 """ 453 logging.Handler.__init__(self) 454 self.host = host 455 self.port = port 456 self.sock = None 457 self.closeOnError = 0 458 self.retryTime = None 459 # 460 # Exponential backoff parameters. 461 # 462 self.retryStart = 1.0 463 self.retryMax = 30.0 464 self.retryFactor = 2.0 465 466 def makeSocket(self, timeout=1): 467 """ 468 A factory method which allows subclasses to define the precise 469 type of socket they want. 470 """ 471 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 472 if hasattr(s, 'settimeout'): 473 s.settimeout(timeout) 474 s.connect((self.host, self.port)) 475 return s 476 477 def createSocket(self): 478 """ 479 Try to create a socket, using an exponential backoff with 480 a max retry time. Thanks to Robert Olson for the original patch 481 (SF #815911) which has been slightly refactored. 482 """ 483 now = time.time() 484 # Either retryTime is None, in which case this 485 # is the first time back after a disconnect, or 486 # we've waited long enough. 487 if self.retryTime is None: 488 attempt = 1 489 else: 490 attempt = (now >= self.retryTime) 491 if attempt: 492 try: 493 self.sock = self.makeSocket() 494 self.retryTime = None # next time, no delay before trying 495 except socket.error: 496 #Creation failed, so set the retry time and return. 497 if self.retryTime is None: 498 self.retryPeriod = self.retryStart 499 else: 500 self.retryPeriod = self.retryPeriod * self.retryFactor 501 if self.retryPeriod > self.retryMax: 502 self.retryPeriod = self.retryMax 503 self.retryTime = now + self.retryPeriod 504 505 def send(self, s): 506 """ 507 Send a pickled string to the socket. 508 509 This function allows for partial sends which can happen when the 510 network is busy. 511 """ 512 if self.sock is None: 513 self.createSocket() 514 #self.sock can be None either because we haven't reached the retry 515 #time yet, or because we have reached the retry time and retried, 516 #but are still unable to connect. 517 if self.sock: 518 try: 519 if hasattr(self.sock, "sendall"): 520 self.sock.sendall(s) 521 else: 522 sentsofar = 0 523 left = len(s) 524 while left > 0: 525 sent = self.sock.send(s[sentsofar:]) 526 sentsofar = sentsofar + sent 527 left = left - sent 528 except socket.error: 529 self.sock.close() 530 self.sock = None # so we can call createSocket next time 531 532 def makePickle(self, record): 533 """ 534 Pickles the record in binary format with a length prefix, and 535 returns it ready for transmission across the socket. 536 """ 537 ei = record.exc_info 538 if ei: 539 # just to get traceback text into record.exc_text ... 540 dummy = self.format(record) 541 record.exc_info = None # to avoid Unpickleable error 542 # See issue #14436: If msg or args are objects, they may not be 543 # available on the receiving end. So we convert the msg % args 544 # to a string, save it as msg and zap the args. 545 d = dict(record.__dict__) 546 d['msg'] = record.getMessage() 547 d['args'] = None 548 s = cPickle.dumps(d, 1) 549 if ei: 550 record.exc_info = ei # for next handler 551 slen = struct.pack(">L", len(s)) 552 return slen + s 553 554 def handleError(self, record): 555 """ 556 Handle an error during logging. 557 558 An error has occurred during logging. Most likely cause - 559 connection lost. Close the socket so that we can retry on the 560 next event. 561 """ 562 if self.closeOnError and self.sock: 563 self.sock.close() 564 self.sock = None #try to reconnect next time 565 else: 566 logging.Handler.handleError(self, record) 567 568 def emit(self, record): 569 """ 570 Emit a record. 571 572 Pickles the record and writes it to the socket in binary format. 573 If there is an error with the socket, silently drop the packet. 574 If there was a problem with the socket, re-establishes the 575 socket. 576 """ 577 try: 578 s = self.makePickle(record) 579 self.send(s) 580 except (KeyboardInterrupt, SystemExit): 581 raise 582 except: 583 self.handleError(record) 584 585 def close(self): 586 """ 587 Closes the socket. 588 """ 589 self.acquire() 590 try: 591 if self.sock: 592 self.sock.close() 593 self.sock = None 594 finally: 595 self.release() 596 logging.Handler.close(self) 597 598 class DatagramHandler(SocketHandler): 599 """ 600 A handler class which writes logging records, in pickle format, to 601 a datagram socket. The pickle which is sent is that of the LogRecord's 602 attribute dictionary (__dict__), so that the receiver does not need to 603 have the logging module installed in order to process the logging event. 604 605 To unpickle the record at the receiving end into a LogRecord, use the 606 makeLogRecord function. 607 608 """ 609 def __init__(self, host, port): 610 """ 611 Initializes the handler with a specific host address and port. 612 """ 613 SocketHandler.__init__(self, host, port) 614 self.closeOnError = 0 615 616 def makeSocket(self): 617 """ 618 The factory method of SocketHandler is here overridden to create 619 a UDP socket (SOCK_DGRAM). 620 """ 621 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 622 return s 623 624 def send(self, s): 625 """ 626 Send a pickled string to a socket. 627 628 This function no longer allows for partial sends which can happen 629 when the network is busy - UDP does not guarantee delivery and 630 can deliver packets out of sequence. 631 """ 632 if self.sock is None: 633 self.createSocket() 634 self.sock.sendto(s, (self.host, self.port)) 635 636 class SysLogHandler(logging.Handler): 637 """ 638 A handler class which sends formatted logging records to a syslog 639 server. Based on Sam Rushing's syslog module: 640 http://www.nightmare.com/squirl/python-ext/misc/syslog.py 641 Contributed by Nicolas Untz (after which minor refactoring changes 642 have been made). 643 """ 644 645 # from <linux/sys/syslog.h>: 646 # ====================================================================== 647 # priorities/facilities are encoded into a single 32-bit quantity, where 648 # the bottom 3 bits are the priority (0-7) and the top 28 bits are the 649 # facility (0-big number). Both the priorities and the facilities map 650 # roughly one-to-one to strings in the syslogd(8) source code. This 651 # mapping is included in this file. 652 # 653 # priorities (these are ordered) 654 655 LOG_EMERG = 0 # system is unusable 656 LOG_ALERT = 1 # action must be taken immediately 657 LOG_CRIT = 2 # critical conditions 658 LOG_ERR = 3 # error conditions 659 LOG_WARNING = 4 # warning conditions 660 LOG_NOTICE = 5 # normal but significant condition 661 LOG_INFO = 6 # informational 662 LOG_DEBUG = 7 # debug-level messages 663 664 # facility codes 665 LOG_KERN = 0 # kernel messages 666 LOG_USER = 1 # random user-level messages 667 LOG_MAIL = 2 # mail system 668 LOG_DAEMON = 3 # system daemons 669 LOG_AUTH = 4 # security/authorization messages 670 LOG_SYSLOG = 5 # messages generated internally by syslogd 671 LOG_LPR = 6 # line printer subsystem 672 LOG_NEWS = 7 # network news subsystem 673 LOG_UUCP = 8 # UUCP subsystem 674 LOG_CRON = 9 # clock daemon 675 LOG_AUTHPRIV = 10 # security/authorization messages (private) 676 LOG_FTP = 11 # FTP daemon 677 678 # other codes through 15 reserved for system use 679 LOG_LOCAL0 = 16 # reserved for local use 680 LOG_LOCAL1 = 17 # reserved for local use 681 LOG_LOCAL2 = 18 # reserved for local use 682 LOG_LOCAL3 = 19 # reserved for local use 683 LOG_LOCAL4 = 20 # reserved for local use 684 LOG_LOCAL5 = 21 # reserved for local use 685 LOG_LOCAL6 = 22 # reserved for local use 686 LOG_LOCAL7 = 23 # reserved for local use 687 688 priority_names = { 689 "alert": LOG_ALERT, 690 "crit": LOG_CRIT, 691 "critical": LOG_CRIT, 692 "debug": LOG_DEBUG, 693 "emerg": LOG_EMERG, 694 "err": LOG_ERR, 695 "error": LOG_ERR, # DEPRECATED 696 "info": LOG_INFO, 697 "notice": LOG_NOTICE, 698 "panic": LOG_EMERG, # DEPRECATED 699 "warn": LOG_WARNING, # DEPRECATED 700 "warning": LOG_WARNING, 701 } 702 703 facility_names = { 704 "auth": LOG_AUTH, 705 "authpriv": LOG_AUTHPRIV, 706 "cron": LOG_CRON, 707 "daemon": LOG_DAEMON, 708 "ftp": LOG_FTP, 709 "kern": LOG_KERN, 710 "lpr": LOG_LPR, 711 "mail": LOG_MAIL, 712 "news": LOG_NEWS, 713 "security": LOG_AUTH, # DEPRECATED 714 "syslog": LOG_SYSLOG, 715 "user": LOG_USER, 716 "uucp": LOG_UUCP, 717 "local0": LOG_LOCAL0, 718 "local1": LOG_LOCAL1, 719 "local2": LOG_LOCAL2, 720 "local3": LOG_LOCAL3, 721 "local4": LOG_LOCAL4, 722 "local5": LOG_LOCAL5, 723 "local6": LOG_LOCAL6, 724 "local7": LOG_LOCAL7, 725 } 726 727 #The map below appears to be trivially lowercasing the key. However, 728 #there's more to it than meets the eye - in some locales, lowercasing 729 #gives unexpected results. See SF #1524081: in the Turkish locale, 730 #"INFO".lower() != "info" 731 priority_map = { 732 "DEBUG" : "debug", 733 "INFO" : "info", 734 "WARNING" : "warning", 735 "ERROR" : "error", 736 "CRITICAL" : "critical" 737 } 738 739 def __init__(self, address=('localhost', SYSLOG_UDP_PORT), 740 facility=LOG_USER, socktype=None): 741 """ 742 Initialize a handler. 743 744 If address is specified as a string, a UNIX socket is used. To log to a 745 local syslogd, "SysLogHandler(address="/dev/log")" can be used. 746 If facility is not specified, LOG_USER is used. If socktype is 747 specified as socket.SOCK_DGRAM or socket.SOCK_STREAM, that specific 748 socket type will be used. For Unix sockets, you can also specify a 749 socktype of None, in which case socket.SOCK_DGRAM will be used, falling 750 back to socket.SOCK_STREAM. 751 """ 752 logging.Handler.__init__(self) 753 754 self.address = address 755 self.facility = facility 756 self.socktype = socktype 757 758 if isinstance(address, basestring): 759 self.unixsocket = 1 760 self._connect_unixsocket(address) 761 else: 762 self.unixsocket = 0 763 if socktype is None: 764 socktype = socket.SOCK_DGRAM 765 self.socket = socket.socket(socket.AF_INET, socktype) 766 if socktype == socket.SOCK_STREAM: 767 self.socket.connect(address) 768 self.socktype = socktype 769 self.formatter = None 770 771 def _connect_unixsocket(self, address): 772 use_socktype = self.socktype 773 if use_socktype is None: 774 use_socktype = socket.SOCK_DGRAM 775 self.socket = socket.socket(socket.AF_UNIX, use_socktype) 776 try: 777 self.socket.connect(address) 778 # it worked, so set self.socktype to the used type 779 self.socktype = use_socktype 780 except socket.error: 781 self.socket.close() 782 if self.socktype is not None: 783 # user didn't specify falling back, so fail 784 raise 785 use_socktype = socket.SOCK_STREAM 786 self.socket = socket.socket(socket.AF_UNIX, use_socktype) 787 try: 788 self.socket.connect(address) 789 # it worked, so set self.socktype to the used type 790 self.socktype = use_socktype 791 except socket.error: 792 self.socket.close() 793 raise 794 795 # curious: when talking to the unix-domain '/dev/log' socket, a 796 # zero-terminator seems to be required. this string is placed 797 # into a class variable so that it can be overridden if 798 # necessary. 799 log_format_string = '<%d>%s\000' 800 801 def encodePriority(self, facility, priority): 802 """ 803 Encode the facility and priority. You can pass in strings or 804 integers - if strings are passed, the facility_names and 805 priority_names mapping dictionaries are used to convert them to 806 integers. 807 """ 808 if isinstance(facility, basestring): 809 facility = self.facility_names[facility] 810 if isinstance(priority, basestring): 811 priority = self.priority_names[priority] 812 return (facility << 3) | priority 813 814 def close (self): 815 """ 816 Closes the socket. 817 """ 818 self.acquire() 819 try: 820 if self.unixsocket: 821 self.socket.close() 822 finally: 823 self.release() 824 logging.Handler.close(self) 825 826 def mapPriority(self, levelName): 827 """ 828 Map a logging level name to a key in the priority_names map. 829 This is useful in two scenarios: when custom levels are being 830 used, and in the case where you can't do a straightforward 831 mapping by lowercasing the logging level name because of locale- 832 specific issues (see SF #1524081). 833 """ 834 return self.priority_map.get(levelName, "warning") 835 836 def emit(self, record): 837 """ 838 Emit a record. 839 840 The record is formatted, and then sent to the syslog server. If 841 exception information is present, it is NOT sent to the server. 842 """ 843 msg = self.format(record) + '\000' 844 """ 845 We need to convert record level to lowercase, maybe this will 846 change in the future. 847 """ 848 prio = '<%d>' % self.encodePriority(self.facility, 849 self.mapPriority(record.levelname)) 850 # Message is a string. Convert to bytes as required by RFC 5424 851 if type(msg) is unicode: 852 msg = msg.encode('utf-8') 853 msg = prio + msg 854 try: 855 if self.unixsocket: 856 try: 857 self.socket.send(msg) 858 except socket.error: 859 self._connect_unixsocket(self.address) 860 self.socket.send(msg) 861 elif self.socktype == socket.SOCK_DGRAM: 862 self.socket.sendto(msg, self.address) 863 else: 864 self.socket.sendall(msg) 865 except (KeyboardInterrupt, SystemExit): 866 raise 867 except: 868 self.handleError(record) 869 870 class SMTPHandler(logging.Handler): 871 """ 872 A handler class which sends an SMTP email for each logging event. 873 """ 874 def __init__(self, mailhost, fromaddr, toaddrs, subject, 875 credentials=None, secure=None): 876 """ 877 Initialize the handler. 878 879 Initialize the instance with the from and to addresses and subject 880 line of the email. To specify a non-standard SMTP port, use the 881 (host, port) tuple format for the mailhost argument. To specify 882 authentication credentials, supply a (username, password) tuple 883 for the credentials argument. To specify the use of a secure 884 protocol (TLS), pass in a tuple for the secure argument. This will 885 only be used when authentication credentials are supplied. The tuple 886 will be either an empty tuple, or a single-value tuple with the name 887 of a keyfile, or a 2-value tuple with the names of the keyfile and 888 certificate file. (This tuple is passed to the `starttls` method). 889 """ 890 logging.Handler.__init__(self) 891 if isinstance(mailhost, tuple): 892 self.mailhost, self.mailport = mailhost 893 else: 894 self.mailhost, self.mailport = mailhost, None 895 if isinstance(credentials, tuple): 896 self.username, self.password = credentials 897 else: 898 self.username = None 899 self.fromaddr = fromaddr 900 if isinstance(toaddrs, basestring): 901 toaddrs = [toaddrs] 902 self.toaddrs = toaddrs 903 self.subject = subject 904 self.secure = secure 905 self._timeout = 5.0 906 907 def getSubject(self, record): 908 """ 909 Determine the subject for the email. 910 911 If you want to specify a subject line which is record-dependent, 912 override this method. 913 """ 914 return self.subject 915 916 def emit(self, record): 917 """ 918 Emit a record. 919 920 Format the record and send it to the specified addressees. 921 """ 922 try: 923 import smtplib 924 from email.utils import formatdate 925 port = self.mailport 926 if not port: 927 port = smtplib.SMTP_PORT 928 smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) 929 msg = self.format(record) 930 msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( 931 self.fromaddr, 932 ",".join(self.toaddrs), 933 self.getSubject(record), 934 formatdate(), msg) 935 if self.username: 936 if self.secure is not None: 937 smtp.ehlo() 938 smtp.starttls(*self.secure) 939 smtp.ehlo() 940 smtp.login(self.username, self.password) 941 smtp.sendmail(self.fromaddr, self.toaddrs, msg) 942 smtp.quit() 943 except (KeyboardInterrupt, SystemExit): 944 raise 945 except: 946 self.handleError(record) 947 948 class NTEventLogHandler(logging.Handler): 949 """ 950 A handler class which sends events to the NT Event Log. Adds a 951 registry entry for the specified application name. If no dllname is 952 provided, win32service.pyd (which contains some basic message 953 placeholders) is used. Note that use of these placeholders will make 954 your event logs big, as the entire message source is held in the log. 955 If you want slimmer logs, you have to pass in the name of your own DLL 956 which contains the message definitions you want to use in the event log. 957 """ 958 def __init__(self, appname, dllname=None, logtype="Application"): 959 logging.Handler.__init__(self) 960 try: 961 import win32evtlogutil, win32evtlog 962 self.appname = appname 963 self._welu = win32evtlogutil 964 if not dllname: 965 dllname = os.path.split(self._welu.__file__) 966 dllname = os.path.split(dllname[0]) 967 dllname = os.path.join(dllname[0], r'win32service.pyd') 968 self.dllname = dllname 969 self.logtype = logtype 970 self._welu.AddSourceToRegistry(appname, dllname, logtype) 971 self.deftype = win32evtlog.EVENTLOG_ERROR_TYPE 972 self.typemap = { 973 logging.DEBUG : win32evtlog.EVENTLOG_INFORMATION_TYPE, 974 logging.INFO : win32evtlog.EVENTLOG_INFORMATION_TYPE, 975 logging.WARNING : win32evtlog.EVENTLOG_WARNING_TYPE, 976 logging.ERROR : win32evtlog.EVENTLOG_ERROR_TYPE, 977 logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, 978 } 979 except ImportError: 980 print("The Python Win32 extensions for NT (service, event "\ 981 "logging) appear not to be available.") 982 self._welu = None 983 984 def getMessageID(self, record): 985 """ 986 Return the message ID for the event record. If you are using your 987 own messages, you could do this by having the msg passed to the 988 logger being an ID rather than a formatting string. Then, in here, 989 you could use a dictionary lookup to get the message ID. This 990 version returns 1, which is the base message ID in win32service.pyd. 991 """ 992 return 1 993 994 def getEventCategory(self, record): 995 """ 996 Return the event category for the record. 997 998 Override this if you want to specify your own categories. This version 999 returns 0. 1000 """ 1001 return 0 1002 1003 def getEventType(self, record): 1004 """ 1005 Return the event type for the record. 1006 1007 Override this if you want to specify your own types. This version does 1008 a mapping using the handler's typemap attribute, which is set up in 1009 __init__() to a dictionary which contains mappings for DEBUG, INFO, 1010 WARNING, ERROR and CRITICAL. If you are using your own levels you will 1011 either need to override this method or place a suitable dictionary in 1012 the handler's typemap attribute. 1013 """ 1014 return self.typemap.get(record.levelno, self.deftype) 1015 1016 def emit(self, record): 1017 """ 1018 Emit a record. 1019 1020 Determine the message ID, event category and event type. Then 1021 log the message in the NT event log. 1022 """ 1023 if self._welu: 1024 try: 1025 id = self.getMessageID(record) 1026 cat = self.getEventCategory(record) 1027 type = self.getEventType(record) 1028 msg = self.format(record) 1029 self._welu.ReportEvent(self.appname, id, cat, type, [msg]) 1030 except (KeyboardInterrupt, SystemExit): 1031 raise 1032 except: 1033 self.handleError(record) 1034 1035 def close(self): 1036 """ 1037 Clean up this handler. 1038 1039 You can remove the application name from the registry as a 1040 source of event log entries. However, if you do this, you will 1041 not be able to see the events as you intended in the Event Log 1042 Viewer - it needs to be able to access the registry to get the 1043 DLL name. 1044 """ 1045 #self._welu.RemoveSourceFromRegistry(self.appname, self.logtype) 1046 logging.Handler.close(self) 1047 1048 class HTTPHandler(logging.Handler): 1049 """ 1050 A class which sends records to a Web server, using either GET or 1051 POST semantics. 1052 """ 1053 def __init__(self, host, url, method="GET"): 1054 """ 1055 Initialize the instance with the host, the request URL, and the method 1056 ("GET" or "POST") 1057 """ 1058 logging.Handler.__init__(self) 1059 method = method.upper() 1060 if method not in ["GET", "POST"]: 1061 raise ValueError("method must be GET or POST") 1062 self.host = host 1063 self.url = url 1064 self.method = method 1065 1066 def mapLogRecord(self, record): 1067 """ 1068 Default implementation of mapping the log record into a dict 1069 that is sent as the CGI data. Overwrite in your class. 1070 Contributed by Franz Glasner. 1071 """ 1072 return record.__dict__ 1073 1074 def emit(self, record): 1075 """ 1076 Emit a record. 1077 1078 Send the record to the Web server as a percent-encoded dictionary 1079 """ 1080 try: 1081 import httplib, urllib 1082 host = self.host 1083 h = httplib.HTTP(host) 1084 url = self.url 1085 data = urllib.urlencode(self.mapLogRecord(record)) 1086 if self.method == "GET": 1087 if (url.find('?') >= 0): 1088 sep = '&' 1089 else: 1090 sep = '?' 1091 url = url + "%c%s" % (sep, data) 1092 h.putrequest(self.method, url) 1093 # support multiple hosts on one IP address... 1094 # need to strip optional :port from host, if present 1095 i = host.find(":") 1096 if i >= 0: 1097 host = host[:i] 1098 h.putheader("Host", host) 1099 if self.method == "POST": 1100 h.putheader("Content-type", 1101 "application/x-www-form-urlencoded") 1102 h.putheader("Content-length", str(len(data))) 1103 h.endheaders(data if self.method == "POST" else None) 1104 h.getreply() #can't do anything with the result 1105 except (KeyboardInterrupt, SystemExit): 1106 raise 1107 except: 1108 self.handleError(record) 1109 1110 class BufferingHandler(logging.Handler): 1111 """ 1112 A handler class which buffers logging records in memory. Whenever each 1113 record is added to the buffer, a check is made to see if the buffer should 1114 be flushed. If it should, then flush() is expected to do what's needed. 1115 """ 1116 def __init__(self, capacity): 1117 """ 1118 Initialize the handler with the buffer size. 1119 """ 1120 logging.Handler.__init__(self) 1121 self.capacity = capacity 1122 self.buffer = [] 1123 1124 def shouldFlush(self, record): 1125 """ 1126 Should the handler flush its buffer? 1127 1128 Returns true if the buffer is up to capacity. This method can be 1129 overridden to implement custom flushing strategies. 1130 """ 1131 return (len(self.buffer) >= self.capacity) 1132 1133 def emit(self, record): 1134 """ 1135 Emit a record. 1136 1137 Append the record. If shouldFlush() tells us to, call flush() to process 1138 the buffer. 1139 """ 1140 self.buffer.append(record) 1141 if self.shouldFlush(record): 1142 self.flush() 1143 1144 def flush(self): 1145 """ 1146 Override to implement custom flushing behaviour. 1147 1148 This version just zaps the buffer to empty. 1149 """ 1150 self.acquire() 1151 try: 1152 self.buffer = [] 1153 finally: 1154 self.release() 1155 1156 def close(self): 1157 """ 1158 Close the handler. 1159 1160 This version just flushes and chains to the parent class' close(). 1161 """ 1162 self.flush() 1163 logging.Handler.close(self) 1164 1165 class MemoryHandler(BufferingHandler): 1166 """ 1167 A handler class which buffers logging records in memory, periodically 1168 flushing them to a target handler. Flushing occurs whenever the buffer 1169 is full, or when an event of a certain severity or greater is seen. 1170 """ 1171 def __init__(self, capacity, flushLevel=logging.ERROR, target=None): 1172 """ 1173 Initialize the handler with the buffer size, the level at which 1174 flushing should occur and an optional target. 1175 1176 Note that without a target being set either here or via setTarget(), 1177 a MemoryHandler is no use to anyone! 1178 """ 1179 BufferingHandler.__init__(self, capacity) 1180 self.flushLevel = flushLevel 1181 self.target = target 1182 1183 def shouldFlush(self, record): 1184 """ 1185 Check for buffer full or a record at the flushLevel or higher. 1186 """ 1187 return (len(self.buffer) >= self.capacity) or \ 1188 (record.levelno >= self.flushLevel) 1189 1190 def setTarget(self, target): 1191 """ 1192 Set the target handler for this handler. 1193 """ 1194 self.target = target 1195 1196 def flush(self): 1197 """ 1198 For a MemoryHandler, flushing means just sending the buffered 1199 records to the target, if there is one. Override if you want 1200 different behaviour. 1201 """ 1202 self.acquire() 1203 try: 1204 if self.target: 1205 for record in self.buffer: 1206 self.target.handle(record) 1207 self.buffer = [] 1208 finally: 1209 self.release() 1210 1211 def close(self): 1212 """ 1213 Flush, set the target to None and lose the buffer. 1214 """ 1215 self.flush() 1216 self.acquire() 1217 try: 1218 self.target = None 1219 BufferingHandler.close(self) 1220 finally: 1221 self.release() 1222