From 1c77b7f84c5fca050980854a677539ba377439dd Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Sat, 10 Oct 2009 20:32:36 +0000 Subject: [PATCH] Issue #7086: Added TCP support to SysLogHandler and tidied up some anachronisms in the code. --- Doc/library/logging.rst | 18 +++++++---- Lib/logging/__init__.py | 66 +++++++++++++++++++++-------------------- Lib/logging/config.py | 31 +++++++++---------- Lib/logging/handlers.py | 49 ++++++++++++++++-------------- Misc/NEWS | 3 ++ 5 files changed, 90 insertions(+), 77 deletions(-) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index ef2f34af95d..06144022af7 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -764,12 +764,12 @@ functions. Does basic configuration for the logging system by creating a :class:`StreamHandler` with a default :class:`Formatter` and adding it to the - root logger. The function does nothing if any handlers have been defined for - the root logger. The functions :func:`debug`, :func:`info`, :func:`warning`, + root logger. The functions :func:`debug`, :func:`info`, :func:`warning`, :func:`error` and :func:`critical` will call :func:`basicConfig` automatically if no handlers are defined for the root logger. - This function does nothing if the root logger already has handlers configured. + This function does nothing if the root logger already has handlers + configured for it. .. versionchanged:: 2.4 Formerly, :func:`basicConfig` did not take any keyword arguments. @@ -2008,16 +2008,22 @@ The :class:`SysLogHandler` class, located in the :mod:`logging.handlers` module, supports sending logging messages to a remote or local Unix syslog. -.. class:: SysLogHandler([address[, facility]]) +.. class:: SysLogHandler([address[, facility[, socktype]]]) Returns a new instance of the :class:`SysLogHandler` class intended to communicate with a remote Unix machine whose address is given by *address* in the form of a ``(host, port)`` tuple. If *address* is not specified, - ``('localhost', 514)`` is used. The address is used to open a UDP socket. An + ``('localhost', 514)`` is used. The address is used to open a socket. An alternative to providing a ``(host, port)`` tuple is providing an address as a string, for example "/dev/log". In this case, a Unix domain socket is used to send the message to the syslog. If *facility* is not specified, - :const:`LOG_USER` is used. + :const:`LOG_USER` is used. The type of socket opened depends on the + *socktype* argument, which defaults to :const:`socket.SOCK_DGRAM` and thus + opens a UDP socket. To open a TCP socket (for use with the newer syslog + daemons such as rsyslog), specify a value of :const:`socket.SOCK_STREAM`. + + .. versionchanged:: 2.7 + *socktype* was added. .. method:: close() diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 092ebc3dad3..0be6ed4b74d 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -23,6 +23,8 @@ Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved. To use, simply 'import logging' and log away! """ +import sys, os, time, cStringIO, traceback, warnings + __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO', 'LogRecord', 'Logger', 'LoggerAdapter', 'NOTSET', 'NullHandler', @@ -31,8 +33,6 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', 'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass', 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning'] -import sys, os, types, time, string, cStringIO, traceback, warnings - try: import codecs except ImportError: @@ -46,12 +46,17 @@ except ImportError: __author__ = "Vinay Sajip " __status__ = "production" -__version__ = "0.5.0.8" -__date__ = "27 April 2009" +__version__ = "0.5.0.9" +__date__ = "09 October 2009" #--------------------------------------------------------------------------- # Miscellaneous module data #--------------------------------------------------------------------------- +try: + unicode + _unicode = True +except NameError: + _unicode = False # # _srcfile is used when walking the stack to check when we've got the first @@ -59,7 +64,7 @@ __date__ = "27 April 2009" # if hasattr(sys, 'frozen'): #support for py2exe _srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:]) -elif string.lower(__file__[-4:]) in ['.pyc', '.pyo']: +elif __file__[-4:].lower() in ['.pyc', '.pyo']: _srcfile = __file__[:-4] + '.py' else: _srcfile = __file__ @@ -71,7 +76,7 @@ def currentframe(): try: raise Exception except: - return sys.exc_traceback.tb_frame.f_back + return sys.exc_info()[2].tb_frame.f_back if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3) # done filching @@ -255,9 +260,7 @@ class LogRecord: # 'Value is %d' instead of 'Value is 0'. # For the use case of passing a dictionary, this should not be a # problem. - if args and len(args) == 1 and ( - type(args[0]) == types.DictType - ) and args[0]: + if args and len(args) == 1 and isinstance(args[0], dict) and args[0]: args = args[0] self.args = args self.levelname = getLevelName(level) @@ -306,11 +309,11 @@ class LogRecord: Return the message for this LogRecord after merging any user-supplied arguments with the message. """ - if not hasattr(types, "UnicodeType"): #if no unicode support... + if not _unicode: #if no unicode support... msg = str(self.msg) else: msg = self.msg - if type(msg) not in (types.UnicodeType, types.StringType): + if not isinstance(msg, basestring): try: msg = str(self.msg) except UnicodeError: @@ -447,7 +450,7 @@ class Formatter: formatException() and appended to the message. """ record.message = record.getMessage() - if string.find(self._fmt,"%(asctime)") >= 0: + if self._fmt.find("%(asctime)") >= 0: record.asctime = self.formatTime(record, self.datefmt) s = self._fmt % record.__dict__ if record.exc_info: @@ -541,7 +544,7 @@ class Filter: return 1 elif self.name == record.name: return 1 - elif string.find(record.name, self.name, 0, self.nlen) != 0: + elif record.name.find(self.name, 0, self.nlen) != 0: return 0 return (record.name[self.nlen] == ".") @@ -667,8 +670,8 @@ class Handler(Filterer): This version is intended to be implemented by subclasses and so raises a NotImplementedError. """ - raise NotImplementedError, 'emit must be implemented '\ - 'by Handler subclasses' + raise NotImplementedError('emit must be implemented ' + 'by Handler subclasses') def handle(self, record): """ @@ -781,7 +784,7 @@ class StreamHandler(Handler): msg = self.format(record) stream = self.stream fs = "%s\n" - if not hasattr(types, "UnicodeType"): #if no unicode support... + if not _unicode: #if no unicode support... stream.write(fs % msg) else: try: @@ -903,8 +906,8 @@ def setLoggerClass(klass): """ if klass != Logger: if not issubclass(klass, Logger): - raise TypeError, "logger not derived from logging.Logger: " + \ - klass.__name__ + raise TypeError("logger not derived from logging.Logger: " + + klass.__name__) global _loggerClass _loggerClass = klass @@ -967,7 +970,7 @@ class Manager: from the specified logger to the root of the logger hierarchy. """ name = alogger.name - i = string.rfind(name, ".") + i = name.rfind(".") rv = None while (i > 0) and not rv: substr = name[:i] @@ -980,7 +983,7 @@ class Manager: else: assert isinstance(obj, PlaceHolder) obj.append(alogger) - i = string.rfind(name, ".", 0, i - 1) + i = name.rfind(".", 0, i - 1) if not rv: rv = self.root alogger.parent = rv @@ -994,7 +997,6 @@ class Manager: namelen = len(name) for c in ph.loggerMap.keys(): #The if means ... if not c.parent.name.startswith(nm) - #if string.find(c.parent.name, nm) <> 0: if c.parent.name[:namelen] != name: alogger.parent = c.parent c.parent = alogger @@ -1090,7 +1092,7 @@ class Logger(Filterer): """ Convenience method for logging an ERROR with exception information. """ - self.error(*((msg,) + args), **{'exc_info': 1}) + self.error(msg, exc_info=1, *args) def critical(self, msg, *args, **kwargs): """ @@ -1115,9 +1117,9 @@ class Logger(Filterer): logger.log(level, "We have a %s", "mysterious problem", exc_info=1) """ - if type(level) != types.IntType: + if not isinstance(level, int): if raiseExceptions: - raise TypeError, "level must be an integer" + raise TypeError("level must be an integer") else: return if self.isEnabledFor(level): @@ -1173,7 +1175,7 @@ class Logger(Filterer): else: fn, lno, func = "(unknown file)", 0, "(unknown function)" if exc_info: - if type(exc_info) != types.TupleType: + if not isinstance(exc_info, tuple): exc_info = sys.exc_info() record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra) self.handle(record) @@ -1449,7 +1451,7 @@ def critical(msg, *args, **kwargs): """ if len(root.handlers) == 0: basicConfig() - root.critical(*((msg,)+args), **kwargs) + root.critical(msg, *args, **kwargs) fatal = critical @@ -1459,14 +1461,14 @@ def error(msg, *args, **kwargs): """ if len(root.handlers) == 0: basicConfig() - root.error(*((msg,)+args), **kwargs) + root.error(msg, *args, **kwargs) def exception(msg, *args): """ Log a message with severity 'ERROR' on the root logger, with exception information. """ - error(*((msg,)+args), **{'exc_info': 1}) + error(msg, exc_info=1, *args) def warning(msg, *args, **kwargs): """ @@ -1474,7 +1476,7 @@ def warning(msg, *args, **kwargs): """ if len(root.handlers) == 0: basicConfig() - root.warning(*((msg,)+args), **kwargs) + root.warning(msg, *args, **kwargs) warn = warning @@ -1484,7 +1486,7 @@ def info(msg, *args, **kwargs): """ if len(root.handlers) == 0: basicConfig() - root.info(*((msg,)+args), **kwargs) + root.info(msg, *args, **kwargs) def debug(msg, *args, **kwargs): """ @@ -1492,7 +1494,7 @@ def debug(msg, *args, **kwargs): """ if len(root.handlers) == 0: basicConfig() - root.debug(*((msg,)+args), **kwargs) + root.debug(msg, *args, **kwargs) def log(level, msg, *args, **kwargs): """ @@ -1500,7 +1502,7 @@ def log(level, msg, *args, **kwargs): """ if len(root.handlers) == 0: basicConfig() - root.log(*((level, msg)+args), **kwargs) + root.log(level, msg, *args, **kwargs) def disable(level): """ diff --git a/Lib/logging/config.py b/Lib/logging/config.py index 3017ae981c9..99403d27c38 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py @@ -19,15 +19,12 @@ Configuration functions for the logging package for Python. The core package is based on PEP 282 and comments thereto in comp.lang.python, and influenced by Apache's log4j system. -Should work under Python versions >= 1.5.2, except that source line -information is not available unless 'sys._getframe()' is. - -Copyright (C) 2001-2008 Vinay Sajip. All Rights Reserved. +Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved. To use, simply 'import logging' and log away! """ -import sys, logging, logging.handlers, string, socket, struct, os, traceback, types +import sys, logging, logging.handlers, socket, struct, os, traceback try: import thread @@ -52,7 +49,7 @@ else: # _listener holds the server object doing the listening _listener = None -def fileConfig(fname, defaults=None, disable_existing_loggers=1): +def fileConfig(fname, defaults=None, disable_existing_loggers=True): """ Read the logging configuration from a ConfigParser-format file. @@ -89,7 +86,7 @@ def fileConfig(fname, defaults=None, disable_existing_loggers=1): def _resolve(name): """Resolve a dotted name to a global object.""" - name = string.split(name, '.') + name = name.split('.') used = name.pop(0) found = __import__(used) for n in name: @@ -102,14 +99,14 @@ def _resolve(name): return found def _strip_spaces(alist): - return map(lambda x: string.strip(x), alist) + return map(lambda x: x.strip(), alist) def _create_formatters(cp): """Create and return formatters""" flist = cp.get("formatters", "keys") if not len(flist): return {} - flist = string.split(flist, ",") + flist = flist.split(",") flist = _strip_spaces(flist) formatters = {} for form in flist: @@ -138,7 +135,7 @@ def _install_handlers(cp, formatters): hlist = cp.get("handlers", "keys") if not len(hlist): return {} - hlist = string.split(hlist, ",") + hlist = hlist.split(",") hlist = _strip_spaces(hlist) handlers = {} fixups = [] #for inter-handler references @@ -181,8 +178,8 @@ def _install_loggers(cp, handlers, disable_existing_loggers): # configure the root first llist = cp.get("loggers", "keys") - llist = string.split(llist, ",") - llist = map(lambda x: string.strip(x), llist) + llist = llist.split(",") + llist = list(map(lambda x: x.strip(), llist)) llist.remove("root") sectname = "logger_root" root = logging.root @@ -195,7 +192,7 @@ def _install_loggers(cp, handlers, disable_existing_loggers): root.removeHandler(h) hlist = cp.get(sectname, "handlers") if len(hlist): - hlist = string.split(hlist, ",") + hlist = hlist.split(",") hlist = _strip_spaces(hlist) for hand in hlist: log.addHandler(handlers[hand]) @@ -209,7 +206,7 @@ def _install_loggers(cp, handlers, disable_existing_loggers): #what's left in existing is the set of loggers #which were in the previous configuration but #which are not in the new configuration. - existing = root.manager.loggerDict.keys() + existing = list(root.manager.loggerDict.keys()) #The list needs to be sorted so that we can #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier @@ -247,7 +244,7 @@ def _install_loggers(cp, handlers, disable_existing_loggers): logger.disabled = 0 hlist = cp.get(sectname, "handlers") if len(hlist): - hlist = string.split(hlist, ",") + hlist = hlist.split(",") hlist = _strip_spaces(hlist) for hand in hlist: logger.addHandler(handlers[hand]) @@ -278,7 +275,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT): stopListening(). """ if not thread: - raise NotImplementedError, "listen() needs threading to work" + raise NotImplementedError("listen() needs threading to work") class ConfigStreamHandler(StreamRequestHandler): """ @@ -321,7 +318,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT): traceback.print_exc() os.remove(file) except socket.error, e: - if type(e.args) != types.TupleType: + if not isinstance(e.args, tuple): raise else: errcode = e.args[0] diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index 9b68b852551..bdf82aff6e9 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -24,7 +24,7 @@ Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved. To use, simply 'import logging.handlers' and log away! """ -import logging, socket, types, os, string, cPickle, struct, time, re +import logging, socket, os, cPickle, struct, time, re from stat import ST_DEV, ST_INO try: @@ -41,6 +41,7 @@ DEFAULT_UDP_LOGGING_PORT = 9021 DEFAULT_HTTP_LOGGING_PORT = 9022 DEFAULT_SOAP_LOGGING_PORT = 9023 SYSLOG_UDP_PORT = 514 +SYSLOG_TCP_PORT = 514 _MIDNIGHT = 24 * 60 * 60 # number of seconds in a day @@ -155,9 +156,9 @@ class TimedRotatingFileHandler(BaseRotatingHandler): If backupCount is > 0, when rollover is done, no more than backupCount files are kept - the oldest ones are deleted. """ - def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0): + def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False): BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay) - self.when = string.upper(when) + self.when = when.upper() self.backupCount = backupCount self.utc = utc # Calculate the real rollover interval, which is just the number of @@ -204,8 +205,6 @@ class TimedRotatingFileHandler(BaseRotatingHandler): self.interval = self.interval * interval # multiply by units requested self.rolloverAt = self.computeRollover(int(time.time())) - #print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime) - def computeRollover(self, currentTime): """ Work out the rollover time based on the specified time. @@ -692,7 +691,8 @@ class SysLogHandler(logging.Handler): "CRITICAL" : "critical" } - def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER): + def __init__(self, address=('localhost', SYSLOG_UDP_PORT), + facility=LOG_USER, socktype=socket.SOCK_DGRAM): """ Initialize a handler. @@ -704,13 +704,16 @@ class SysLogHandler(logging.Handler): self.address = address self.facility = facility - if type(address) == types.StringType: + self.socktype = socktype + + if isinstance(address, basestring): self.unixsocket = 1 self._connect_unixsocket(address) else: self.unixsocket = 0 - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - + self.socket = socket.socket(socket.AF_INET, socktype) + if socktype == socket.SOCK_STREAM: + self.socket.connect(address) self.formatter = None def _connect_unixsocket(self, address): @@ -736,9 +739,9 @@ class SysLogHandler(logging.Handler): priority_names mapping dictionaries are used to convert them to integers. """ - if type(facility) == types.StringType: + if isinstance(facility, basestring): facility = self.facility_names[facility] - if type(priority) == types.StringType: + if isinstance(priority, basestring): priority = self.priority_names[priority] return (facility << 3) | priority @@ -783,8 +786,10 @@ class SysLogHandler(logging.Handler): except socket.error: self._connect_unixsocket(self.address) self.socket.send(msg) - else: + elif self.socktype == socket.SOCK_DGRAM: self.socket.sendto(msg, self.address) + else: + self.socket.sendall(msg) except (KeyboardInterrupt, SystemExit): raise except: @@ -805,16 +810,16 @@ class SMTPHandler(logging.Handler): for the credentials argument. """ logging.Handler.__init__(self) - if type(mailhost) == types.TupleType: + if isinstance(mailhost, tuple): self.mailhost, self.mailport = mailhost else: self.mailhost, self.mailport = mailhost, None - if type(credentials) == types.TupleType: + if isinstance(credentials, tuple): self.username, self.password = credentials else: self.username = None self.fromaddr = fromaddr - if type(toaddrs) == types.StringType: + if isinstance(toaddrs, basestring): toaddrs = [toaddrs] self.toaddrs = toaddrs self.subject = subject @@ -865,7 +870,7 @@ class SMTPHandler(logging.Handler): msg = self.format(record) msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( self.fromaddr, - string.join(self.toaddrs, ","), + ",".join(self.toaddrs), self.getSubject(record), formatdate(), msg) if self.username: @@ -909,8 +914,8 @@ class NTEventLogHandler(logging.Handler): logging.CRITICAL: win32evtlog.EVENTLOG_ERROR_TYPE, } except ImportError: - print "The Python Win32 extensions for NT (service, event "\ - "logging) appear not to be available." + print("The Python Win32 extensions for NT (service, event "\ + "logging) appear not to be available.") self._welu = None def getMessageID(self, record): @@ -988,9 +993,9 @@ class HTTPHandler(logging.Handler): ("GET" or "POST") """ logging.Handler.__init__(self) - method = string.upper(method) + method = method.upper() if method not in ["GET", "POST"]: - raise ValueError, "method must be GET or POST" + raise ValueError("method must be GET or POST") self.host = host self.url = url self.method = method @@ -1016,7 +1021,7 @@ class HTTPHandler(logging.Handler): url = self.url data = urllib.urlencode(self.mapLogRecord(record)) if self.method == "GET": - if (string.find(url, '?') >= 0): + if (url.find('?') >= 0): sep = '&' else: sep = '?' @@ -1024,7 +1029,7 @@ class HTTPHandler(logging.Handler): h.putrequest(self.method, url) # support multiple hosts on one IP address... # need to strip optional :port from host, if present - i = string.find(host, ":") + i = host.find(":") if i >= 0: host = host[:i] h.putheader("Host", host) diff --git a/Misc/NEWS b/Misc/NEWS index f3941c09557..bdd657c5cf6 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -398,6 +398,9 @@ Core and Builtins Library ------- +- Issue #7086: Added TCP support to SysLogHandler, and tidied up some + anachronisms in the code which were a relic of 1.5.2 compatibility. + - Issue #7082: When falling back to the MIME 'name' parameter, the correct place to look for it is the Content-Type header.