diff options
Diffstat (limited to 'Lib/logging')
-rw-r--r-- | Lib/logging/__init__.py | 199 | ||||
-rw-r--r-- | Lib/logging/config.py | 24 | ||||
-rw-r--r-- | Lib/logging/handlers.py | 190 |
3 files changed, 259 insertions, 154 deletions
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 5cb2866d75..9f436f3680 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2001-2012 by Vinay Sajip. All Rights Reserved. +# Copyright 2001-2013 by Vinay Sajip. All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, @@ -18,7 +18,7 @@ Logging package for Python. Based on PEP 282 and comments thereto in comp.lang.python. -Copyright (C) 2001-2012 Vinay Sajip. All Rights Reserved. +Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved. To use, simply 'import logging' and log away! """ @@ -36,15 +36,9 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort'] try: - import codecs -except ImportError: - codecs = None - -try: - import _thread as thread import threading -except ImportError: - thread = None +except ImportError: #pragma: no cover + threading = None __author__ = "Vinay Sajip <vinay_sajip@red-dove.com>" __status__ = "production" @@ -65,16 +59,16 @@ else: _srcfile = __file__ _srcfile = os.path.normcase(_srcfile) -# next bit filched from 1.5.2's inspect.py -def currentframe(): - """Return the frame object for the caller's stack frame.""" - try: - raise Exception - except: - return sys.exc_info()[2].tb_frame.f_back -if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3) -# done filching +if hasattr(sys, '_getframe'): + currentframe = lambda: sys._getframe(3) +else: #pragma: no cover + def currentframe(): + """Return the frame object for the caller's stack frame.""" + try: + raise Exception + except: + return sys.exc_info()[2].tb_frame.f_back # _srcfile is only used in conjunction with sys._getframe(). # To provide compatibility with older versions of Python, set _srcfile @@ -92,22 +86,22 @@ _startTime = time.time() #raiseExceptions is used to see if exceptions during handling should be #propagated # -raiseExceptions = 1 +raiseExceptions = True # # If you don't want threading information in the log, set this to zero # -logThreads = 1 +logThreads = True # # If you don't want multiprocessing information in the log, set this to zero # -logMultiprocessing = 1 +logMultiprocessing = True # # If you don't want process information in the log, set this to zero # -logProcesses = 1 +logProcesses = True #--------------------------------------------------------------------------- # Level related stuff @@ -197,9 +191,9 @@ def _checkLevel(level): #the lock would already have been acquired - so we need an RLock. #The same argument applies to Loggers and Manager.loggerDict. # -if thread: +if threading: _lock = threading.RLock() -else: +else: #pragma: no cover _lock = None @@ -252,7 +246,7 @@ class LogRecord(object): # during formatting, we test to see if the arg is present using # 'if self.args:'. If the event being logged is e.g. 'Value is %d' # and if the passed arg fails 'if self.args:' then no formatting - # is done. For example, logger.warn('Value is %d', 0) would log + # is done. For example, logger.warning('Value is %d', 0) would log # 'Value is %d' instead of 'Value is 0'. # For the use case of passing a dictionary, this should not be a # problem. @@ -276,13 +270,13 @@ class LogRecord(object): self.created = ct self.msecs = (ct - int(ct)) * 1000 self.relativeCreated = (self.created - _startTime) * 1000 - if logThreads and thread: - self.thread = thread.get_ident() + if logThreads and threading: + self.thread = threading.get_ident() self.threadName = threading.current_thread().name - else: + else: # pragma: no cover self.thread = None self.threadName = None - if not logMultiprocessing: + if not logMultiprocessing: # pragma: no cover self.processName = None else: self.processName = 'MainProcess' @@ -294,7 +288,7 @@ class LogRecord(object): # for an example try: self.processName = mp.current_process().name - except Exception: + except Exception: #pragma: no cover pass if logProcesses and hasattr(os, 'getpid'): self.process = os.getpid() @@ -394,10 +388,12 @@ class StringTemplateStyle(PercentStyle): def format(self, record): return self._tpl.substitute(**record.__dict__) +BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s" + _STYLES = { - '%': PercentStyle, - '{': StrFormatStyle, - '$': StringTemplateStyle + '%': (PercentStyle, BASIC_FORMAT), + '{': (StrFormatStyle, '{levelname}:{name}:{message}'), + '$': (StringTemplateStyle, '${levelname}:${name}:${message}'), } class Formatter(object): @@ -462,10 +458,13 @@ class Formatter(object): if style not in _STYLES: raise ValueError('Style must be one of: %s' % ','.join( _STYLES.keys())) - self._style = _STYLES[style](fmt) + self._style = _STYLES[style][0](fmt) self._fmt = self._style._fmt self.datefmt = datefmt + default_time_format = '%Y-%m-%d %H:%M:%S' + default_msec_format = '%s,%03d' + def formatTime(self, record, datefmt=None): """ Return the creation time of the specified LogRecord as formatted text. @@ -488,8 +487,8 @@ class Formatter(object): if datefmt: s = time.strftime(datefmt, ct) else: - t = time.strftime("%Y-%m-%d %H:%M:%S", ct) - s = "%s,%03d" % (t, record.msecs) # the use of % here is internal + t = time.strftime(self.default_time_format, ct) + s = self.default_msec_format % (t, record.msecs) return s def formatException(self, ei): @@ -642,11 +641,11 @@ class Filter(object): yes. If deemed appropriate, the record may be modified in-place. """ if self.nlen == 0: - return 1 + return True elif self.name == record.name: - return 1 + return True elif record.name.find(self.name, 0, self.nlen) != 0: - return 0 + return False return (record.name[self.nlen] == ".") class Filterer(object): @@ -686,14 +685,14 @@ class Filterer(object): Allow filters to be just callables. """ - rv = 1 + rv = True for f in self.filters: if hasattr(f, 'filter'): result = f.filter(record) else: result = f(record) # assume callable - will raise if not if not result: - rv = 0 + rv = False break return rv @@ -772,9 +771,9 @@ class Handler(Filterer): """ Acquire a thread lock for serializing access to the underlying I/O. """ - if thread: + if threading: self.lock = threading.RLock() - else: + else: #pragma: no cover self.lock = None def acquire(self): @@ -793,7 +792,7 @@ class Handler(Filterer): def setLevel(self, level): """ - Set the logging level of this handler. + Set the logging level of this handler. level must be an int or a str. """ self.level = _checkLevel(level) @@ -889,7 +888,7 @@ class Handler(Filterer): None, sys.stderr) sys.stderr.write('Logged from file %s, line %s\n' % ( record.filename, record.lineno)) - except IOError: + except IOError: #pragma: no cover pass # see issue 5971 finally: del ei @@ -942,7 +941,7 @@ class StreamHandler(Handler): stream.write(msg) stream.write(self.terminator) self.flush() - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -951,17 +950,16 @@ class FileHandler(StreamHandler): """ A handler class which writes formatted logging records to disk files. """ - def __init__(self, filename, mode='a', encoding=None, delay=0): + def __init__(self, filename, mode='a', encoding=None, delay=False): """ Open the specified file and use it as the stream for logging. """ #keep the absolute path, otherwise derived classes which use this #may come a cropper when the current directory changes - if codecs is None: - encoding = None self.baseFilename = os.path.abspath(filename) self.mode = mode self.encoding = encoding + self.delay = delay if delay: #We don't open the stream, but we still need to call the #Handler constructor to set level, formatter, lock etc. @@ -980,8 +978,10 @@ class FileHandler(StreamHandler): self.flush() if hasattr(self.stream, "close"): self.stream.close() - StreamHandler.close(self) self.stream = None + # Issue #19523: call unconditionally to + # prevent a handler leak when delay is set + StreamHandler.close(self) finally: self.release() @@ -990,11 +990,7 @@ class FileHandler(StreamHandler): Open the current base file with the (original) mode and encoding. Return the resulting stream. """ - if self.encoding is None: - stream = open(self.baseFilename, self.mode) - else: - stream = codecs.open(self.baseFilename, self.mode, self.encoding) - return stream + return open(self.baseFilename, self.mode, encoding=self.encoding) def emit(self, record): """ @@ -1206,13 +1202,13 @@ class Logger(Filterer): self.name = name self.level = _checkLevel(level) self.parent = None - self.propagate = 1 + self.propagate = True self.handlers = [] - self.disabled = 0 + self.disabled = False def setLevel(self, level): """ - Set the logging level of this logger. + Set the logging level of this logger. level must be an int or a str. """ self.level = _checkLevel(level) @@ -1252,7 +1248,10 @@ class Logger(Filterer): if self.isEnabledFor(WARNING): self._log(WARNING, msg, args, **kwargs) - warn = warning + def warn(self, msg, *args, **kwargs): + warnings.warn("The 'warn' method is deprecated, " + "use 'warning' instead", DeprecationWarning, 2) + self.warning(msg, *args, **kwargs) def error(self, msg, *args, **kwargs): """ @@ -1361,9 +1360,9 @@ class Logger(Filterer): #IronPython can use logging. try: fn, lno, func, sinfo = self.findCaller(stack_info) - except ValueError: + except ValueError: # pragma: no cover fn, lno, func = "(unknown file)", 0, "(unknown function)" - else: + else: # pragma: no cover fn, lno, func = "(unknown file)", 0, "(unknown function)" if exc_info: if not isinstance(exc_info, tuple): @@ -1475,7 +1474,7 @@ class Logger(Filterer): Is this logger enabled for level 'level'? """ if self.manager.disable >= level: - return 0 + return False return level >= self.getEffectiveLevel() def getChild(self, suffix): @@ -1565,7 +1564,10 @@ class LoggerAdapter(object): """ self.log(WARNING, msg, *args, **kwargs) - warn = warning + def warn(self, msg, *args, **kwargs): + warnings.warn("The 'warn' method is deprecated, " + "use 'warning' instead", DeprecationWarning, 2) + self.warning(msg, *args, **kwargs) def error(self, msg, *args, **kwargs): """ @@ -1577,7 +1579,7 @@ class LoggerAdapter(object): """ Delegate an exception call to the underlying logger. """ - kwargs["exc_info"] = 1 + kwargs["exc_info"] = True self.log(ERROR, msg, *args, **kwargs) def critical(self, msg, *args, **kwargs): @@ -1629,8 +1631,6 @@ Logger.manager = Manager(Logger.root) # Configuration classes and functions #--------------------------------------------------------------------------- -BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s" - def basicConfig(**kwargs): """ Do basic configuration for the logging system. @@ -1660,6 +1660,10 @@ def basicConfig(**kwargs): stream Use the specified stream to initialize the StreamHandler. Note that this argument is incompatible with 'filename' - if both are present, 'stream' is ignored. + handlers If specified, this should be an iterable of already created + handlers, which will be added to the root handler. Any handler + in the list which does not have a formatter assigned will be + assigned the formatter created in this function. Note that you could specify a stream created using open(filename, mode) rather than passing the filename and mode in. However, it should be @@ -1667,33 +1671,53 @@ def basicConfig(**kwargs): using sys.stdout or sys.stderr), whereas FileHandler closes its stream when the handler is closed. - .. versionchanged: 3.2 + .. versionchanged:: 3.2 Added the ``style`` parameter. + + .. versionchanged:: 3.3 + Added the ``handlers`` parameter. A ``ValueError`` is now thrown for + incompatible arguments (e.g. ``handlers`` specified together with + ``filename``/``filemode``, or ``filename``/``filemode`` specified + together with ``stream``, or ``handlers`` specified together with + ``stream``. """ # Add thread safety in case someone mistakenly calls # basicConfig() from multiple threads _acquireLock() try: if len(root.handlers) == 0: - filename = kwargs.pop("filename", None) - if filename: - mode = kwargs.pop("filemode", 'a') - hdlr = FileHandler(filename, mode) + handlers = kwargs.get("handlers") + if handlers is None: + if "stream" in kwargs and "filename" in kwargs: + raise ValueError("'stream' and 'filename' should not be " + "specified together") else: - stream = kwargs.pop("stream", None) - hdlr = StreamHandler(stream) - fs = kwargs.pop("format", BASIC_FORMAT) - dfs = kwargs.pop("datefmt", None) - style = kwargs.pop("style", '%') + if "stream" in kwargs or "filename" in kwargs: + raise ValueError("'stream' or 'filename' should not be " + "specified together with 'handlers'") + if handlers is None: + filename = kwargs.get("filename") + if filename: + mode = kwargs.get("filemode", 'a') + h = FileHandler(filename, mode) + else: + stream = kwargs.get("stream") + h = StreamHandler(stream) + handlers = [h] + dfs = kwargs.get("datefmt", None) + style = kwargs.get("style", '%') + if style not in _STYLES: + raise ValueError('Style must be one of: %s' % ','.join( + _STYLES.keys())) + fs = kwargs.get("format", _STYLES[style][1]) fmt = Formatter(fs, dfs, style) - hdlr.setFormatter(fmt) - root.addHandler(hdlr) - level = kwargs.pop("level", None) + for h in handlers: + if h.formatter is None: + h.setFormatter(fmt) + root.addHandler(h) + level = kwargs.get("level") if level is not None: root.setLevel(level) - if kwargs: - s = ', '.join(kwargs.keys()) - raise ValueError('Unexpected in keyword arguments: %s' % s) finally: _releaseLock() @@ -1754,7 +1778,10 @@ def warning(msg, *args, **kwargs): basicConfig() root.warning(msg, *args, **kwargs) -warn = warning +def warn(msg, *args, **kwargs): + warnings.warn("The 'warn' function is deprecated, " + "use 'warning' instead", DeprecationWarning, 2) + warning(msg, *args, **kwargs) def info(msg, *args, **kwargs): """ @@ -1839,10 +1866,10 @@ class NullHandler(Handler): package. """ def handle(self, record): - pass + """Stub.""" def emit(self, record): - pass + """Stub.""" def createLock(self): self.lock = None diff --git a/Lib/logging/config.py b/Lib/logging/config.py index 560ca379a3..188061449d 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py @@ -24,13 +24,13 @@ Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved. To use, simply 'import logging' and log away! """ -import sys, logging, logging.handlers, socket, struct, os, traceback, re -import types, io +import sys, logging, logging.handlers, socket, struct, traceback, re +import io try: import _thread as thread import threading -except ImportError: +except ImportError: #pragma: no cover thread = None from socketserver import ThreadingTCPServer, StreamRequestHandler @@ -98,9 +98,6 @@ def _resolve(name): def _strip_spaces(alist): return map(lambda x: x.strip(), alist) -def _encoded(s): - return s if isinstance(s, str) else s.encode('utf-8') - def _create_formatters(cp): """Create and return formatters""" flist = cp["formatters"]["keys"] @@ -215,7 +212,7 @@ def _install_loggers(cp, handlers, disable_existing): #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier #to find the child loggers. - existing.sort(key=_encoded) + existing.sort() #We'll keep the list of existing loggers #which are children of named loggers here... child_loggers = [] @@ -603,7 +600,7 @@ class DictConfigurator(BaseConfigurator): #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier #to find the child loggers. - existing.sort(key=_encoded) + existing.sort() #We'll keep the list of existing loggers #which are children of named loggers here... child_loggers = [] @@ -672,7 +669,8 @@ class DictConfigurator(BaseConfigurator): else: fmt = config.get('format', None) dfmt = config.get('datefmt', None) - result = logging.Formatter(fmt, dfmt) + style = config.get('style', '%') + result = logging.Formatter(fmt, dfmt, style) return result def configure_filter(self, config): @@ -694,6 +692,7 @@ class DictConfigurator(BaseConfigurator): def configure_handler(self, config): """Configure a handler from a dictionary.""" + config_copy = dict(config) # for restoring in case of error formatter = config.pop('formatter', None) if formatter: try: @@ -717,7 +716,7 @@ class DictConfigurator(BaseConfigurator): try: th = self.config['handlers'][config['target']] if not isinstance(th, logging.Handler): - config['class'] = cname # restore for deferred configuration + config.update(config_copy) # restore for deferred cfg raise TypeError('target not configured yet') config['target'] = th except Exception as e: @@ -806,7 +805,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT): and which you can join() when appropriate. To stop the server, call stopListening(). """ - if not thread: + if not thread: #pragma: no cover raise NotImplementedError("listen() needs threading to work") class ConfigStreamHandler(StreamRequestHandler): @@ -824,7 +823,6 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT): struct.pack(">L", n), followed by the config file. Uses fileConfig() to do the grunt work. """ - import tempfile try: conn = self.connection chunk = conn.recv(4) @@ -845,7 +843,7 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT): file = io.StringIO(chunk) try: fileConfig(file) - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: traceback.print_exc() diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index 8349d3a7fb..ddec7dd9d0 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -1,4 +1,4 @@ -# Copyright 2001-2012 by Vinay Sajip. All Rights Reserved. +# Copyright 2001-2013 by Vinay Sajip. All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, @@ -18,24 +18,20 @@ Additional handlers for the logging package for Python. The core package is based on PEP 282 and comments thereto in comp.lang.python. -Copyright (C) 2001-2012 Vinay Sajip. All Rights Reserved. +Copyright (C) 2001-2013 Vinay Sajip. All Rights Reserved. To use, simply 'import logging.handlers' and log away! """ import errno, logging, socket, os, pickle, struct, time, re +from codecs import BOM_UTF8 from stat import ST_DEV, ST_INO, ST_MTIME import queue try: import threading -except ImportError: +except ImportError: #pragma: no cover threading = None -try: - import codecs -except ImportError: - codecs = None - # # Some constants... # @@ -55,15 +51,15 @@ class BaseRotatingHandler(logging.FileHandler): Not meant to be instantiated directly. Instead, use RotatingFileHandler or TimedRotatingFileHandler. """ - def __init__(self, filename, mode, encoding=None, delay=0): + def __init__(self, filename, mode, encoding=None, delay=False): """ Use the specified filename for streamed logging """ - if codecs is None: - encoding = None logging.FileHandler.__init__(self, filename, mode, encoding, delay) self.mode = mode self.encoding = encoding + self.namer = None + self.rotator = None def emit(self, record): """ @@ -76,17 +72,57 @@ class BaseRotatingHandler(logging.FileHandler): if self.shouldRollover(record): self.doRollover() logging.FileHandler.emit(self, record) - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) + def rotation_filename(self, default_name): + """ + Modify the filename of a log file when rotating. + + This is provided so that a custom filename can be provided. + + The default implementation calls the 'namer' attribute of the + handler, if it's callable, passing the default name to + it. If the attribute isn't callable (the default is None), the name + is returned unchanged. + + :param default_name: The default name for the log file. + """ + if not callable(self.namer): + result = default_name + else: + result = self.namer(default_name) + return result + + def rotate(self, source, dest): + """ + When rotating, rotate the current log. + + The default implementation calls the 'rotator' attribute of the + handler, if it's callable, passing the source and dest arguments to + it. If the attribute isn't callable (the default is None), the source + is simply renamed to the destination. + + :param source: The source filename. This is normally the base + filename, e.g. 'test.log' + :param dest: The destination filename. This is normally + what the source is rotated to, e.g. 'test.log.1'. + """ + if not callable(self.rotator): + # Issue 18940: A file may not have been created if delay is True. + if os.path.exists(source): + os.rename(source, dest) + else: + self.rotator(source, dest) + class RotatingFileHandler(BaseRotatingHandler): """ Handler for logging to a set of files, which switches from one file to the next when the current file reaches a certain size. """ - def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0): + def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False): """ Open the specified file and use it as the stream for logging. @@ -127,17 +163,19 @@ class RotatingFileHandler(BaseRotatingHandler): self.stream = None if self.backupCount > 0: for i in range(self.backupCount - 1, 0, -1): - sfn = "%s.%d" % (self.baseFilename, i) - dfn = "%s.%d" % (self.baseFilename, i + 1) + sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i)) + dfn = self.rotation_filename("%s.%d" % (self.baseFilename, + i + 1)) if os.path.exists(sfn): if os.path.exists(dfn): os.remove(dfn) os.rename(sfn, dfn) - dfn = self.baseFilename + ".1" + dfn = self.rotation_filename(self.baseFilename + ".1") if os.path.exists(dfn): os.remove(dfn) - os.rename(self.baseFilename, dfn) - self.stream = self._open() + self.rotate(self.baseFilename, dfn) + if not self.delay: + self.stream = self._open() def shouldRollover(self, record): """ @@ -183,19 +221,19 @@ class TimedRotatingFileHandler(BaseRotatingHandler): if self.when == 'S': self.interval = 1 # one second self.suffix = "%Y-%m-%d_%H-%M-%S" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$" + self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$" elif self.when == 'M': self.interval = 60 # one minute self.suffix = "%Y-%m-%d_%H-%M" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$" + self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$" elif self.when == 'H': self.interval = 60 * 60 # one hour self.suffix = "%Y-%m-%d_%H" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$" + self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$" elif self.when == 'D' or self.when == 'MIDNIGHT': self.interval = 60 * 60 * 24 # one day self.suffix = "%Y-%m-%d" - self.extMatch = r"^\d{4}-\d{2}-\d{2}$" + self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$" elif self.when.startswith('W'): self.interval = 60 * 60 * 24 * 7 # one week if len(self.when) != 2: @@ -204,7 +242,7 @@ class TimedRotatingFileHandler(BaseRotatingHandler): raise ValueError("Invalid day specified for weekly rollover: %s" % self.when) self.dayOfWeek = int(self.when[1]) self.suffix = "%Y-%m-%d" - self.extMatch = r"^\d{4}-\d{2}-\d{2}$" + self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$" else: raise ValueError("Invalid rollover interval specified: %s" % self.when) @@ -337,14 +375,16 @@ class TimedRotatingFileHandler(BaseRotatingHandler): else: addend = -3600 timeTuple = time.localtime(t + addend) - dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple) + dfn = self.rotation_filename(self.baseFilename + "." + + time.strftime(self.suffix, timeTuple)) if os.path.exists(dfn): os.remove(dfn) - os.rename(self.baseFilename, dfn) + self.rotate(self.baseFilename, dfn) if self.backupCount > 0: for s in self.getFilesToDelete(): os.remove(s) - self.stream = self._open() + if not self.delay: + self.stream = self._open() newRolloverAt = self.computeRollover(currentTime) while newRolloverAt <= currentTime: newRolloverAt = newRolloverAt + self.interval @@ -379,7 +419,7 @@ class WatchedFileHandler(logging.FileHandler): This handler is based on a suggestion and patch by Chad J. Schroeder. """ - def __init__(self, filename, mode='a', encoding=None, delay=0): + def __init__(self, filename, mode='a', encoding=None, delay=False): logging.FileHandler.__init__(self, filename, mode, encoding, delay) self.dev, self.ino = -1, -1 self._statstream() @@ -438,15 +478,15 @@ class SocketHandler(logging.Handler): """ Initializes the handler with a specific host address and port. - The attribute 'closeOnError' is set to 1 - which means that if - a socket error occurs, the socket is silently closed and then - reopened on the next logging call. + When the attribute *closeOnError* is set to True - if a socket error + occurs, the socket is silently closed and then reopened on the next + logging call. """ logging.Handler.__init__(self) self.host = host self.port = port self.sock = None - self.closeOnError = 0 + self.closeOnError = False self.retryTime = None # # Exponential backoff parameters. @@ -463,8 +503,12 @@ class SocketHandler(logging.Handler): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if hasattr(s, 'settimeout'): s.settimeout(timeout) - s.connect((self.host, self.port)) - return s + try: + s.connect((self.host, self.port)) + return s + except socket.error: + s.close() + raise def createSocket(self): """ @@ -477,7 +521,7 @@ class SocketHandler(logging.Handler): # is the first time back after a disconnect, or # we've waited long enough. if self.retryTime is None: - attempt = 1 + attempt = True else: attempt = (now >= self.retryTime) if attempt: @@ -510,14 +554,14 @@ class SocketHandler(logging.Handler): try: if hasattr(self.sock, "sendall"): self.sock.sendall(s) - else: + else: #pragma: no cover sentsofar = 0 left = len(s) while left > 0: sent = self.sock.send(s[sentsofar:]) sentsofar = sentsofar + sent left = left - sent - except socket.error: + except socket.error: #pragma: no cover self.sock.close() self.sock = None # so we can call createSocket next time @@ -567,7 +611,7 @@ class SocketHandler(logging.Handler): try: s = self.makePickle(record) self.send(s) - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -601,7 +645,7 @@ class DatagramHandler(SocketHandler): Initializes the handler with a specific host address and port. """ SocketHandler.__init__(self, host, port) - self.closeOnError = 0 + self.closeOnError = False def makeSocket(self): """ @@ -727,7 +771,7 @@ class SysLogHandler(logging.Handler): } def __init__(self, address=('localhost', SYSLOG_UDP_PORT), - facility=LOG_USER, socktype=socket.SOCK_DGRAM): + facility=LOG_USER, socktype=None): """ Initialize a handler. @@ -742,22 +786,41 @@ class SysLogHandler(logging.Handler): self.socktype = socktype if isinstance(address, str): - self.unixsocket = 1 + self.unixsocket = True self._connect_unixsocket(address) else: - self.unixsocket = 0 + self.unixsocket = False + if socktype is None: + socktype = socket.SOCK_DGRAM self.socket = socket.socket(socket.AF_INET, socktype) if socktype == socket.SOCK_STREAM: self.socket.connect(address) + self.socktype = socktype self.formatter = None def _connect_unixsocket(self, address): - self.socket = socket.socket(socket.AF_UNIX, self.socktype) + use_socktype = self.socktype + if use_socktype is None: + use_socktype = socket.SOCK_DGRAM + self.socket = socket.socket(socket.AF_UNIX, use_socktype) try: self.socket.connect(address) + # it worked, so set self.socktype to the used type + self.socktype = use_socktype except socket.error: self.socket.close() - raise + if self.socktype is not None: + # user didn't specify falling back, so fail + raise + use_socktype = socket.SOCK_STREAM + self.socket = socket.socket(socket.AF_UNIX, use_socktype) + try: + self.socket.connect(address) + # it worked, so set self.socktype to the used type + self.socktype = use_socktype + except socket.error: + self.socket.close() + raise def encodePriority(self, facility, priority): """ @@ -778,8 +841,7 @@ class SysLogHandler(logging.Handler): """ self.acquire() try: - if self.unixsocket: - self.socket.close() + self.socket.close() logging.Handler.close(self) finally: self.release() @@ -794,6 +856,7 @@ class SysLogHandler(logging.Handler): """ return self.priority_map.get(levelName, "warning") + ident = '' # prepended to all messages append_nul = True # some old syslog daemons expect a NUL terminator def emit(self, record): @@ -804,6 +867,8 @@ class SysLogHandler(logging.Handler): exception information is present, it is NOT sent to the server. """ msg = self.format(record) + if self.ident: + msg = self.ident + msg if self.append_nul: msg += '\000' """ @@ -821,13 +886,14 @@ class SysLogHandler(logging.Handler): try: self.socket.send(msg) except socket.error: + self.socket.close() self._connect_unixsocket(self.address) self.socket.send(msg) elif self.socktype == socket.SOCK_DGRAM: self.socket.sendto(msg, self.address) else: self.socket.sendall(msg) - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -837,7 +903,7 @@ class SMTPHandler(logging.Handler): A handler class which sends an SMTP email for each logging event. """ def __init__(self, mailhost, fromaddr, toaddrs, subject, - credentials=None, secure=None): + credentials=None, secure=None, timeout=5.0): """ Initialize the handler. @@ -851,6 +917,8 @@ class SMTPHandler(logging.Handler): will be either an empty tuple, or a single-value tuple with the name of a keyfile, or a 2-value tuple with the names of the keyfile and certificate file. (This tuple is passed to the `starttls` method). + A timeout in seconds can be specified for the SMTP connection (the + default is one second). """ logging.Handler.__init__(self) if isinstance(mailhost, tuple): @@ -867,7 +935,7 @@ class SMTPHandler(logging.Handler): self.toaddrs = toaddrs self.subject = subject self.secure = secure - self._timeout = 5.0 + self.timeout = timeout def getSubject(self, record): """ @@ -890,7 +958,7 @@ class SMTPHandler(logging.Handler): port = self.mailport if not port: port = smtplib.SMTP_PORT - smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) + smtp = smtplib.SMTP(self.mailhost, port, timeout=self.timeout) msg = self.format(record) msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( self.fromaddr, @@ -905,7 +973,7 @@ class SMTPHandler(logging.Handler): smtp.login(self.username, self.password) smtp.sendmail(self.fromaddr, self.toaddrs, msg) smtp.quit() - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -992,7 +1060,7 @@ class NTEventLogHandler(logging.Handler): type = self.getEventType(record) msg = self.format(record) self._welu.ReportEvent(self.appname, id, cat, type, [msg]) - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -1075,9 +1143,11 @@ class HTTPHandler(logging.Handler): s = ('u%s:%s' % self.credentials).encode('utf-8') s = 'Basic ' + base64.b64encode(s).strip() h.putheader('Authorization', s) - h.endheaders(data if self.method == "POST" else None) + h.endheaders() + if self.method == "POST": + h.send(data.encode('utf-8')) h.getresponse() #can't do anything with the result - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -1259,7 +1329,7 @@ class QueueHandler(logging.Handler): """ try: self.enqueue(self.prepare(record)) - except (KeyboardInterrupt, SystemExit): + except (KeyboardInterrupt, SystemExit): #pragma: no cover raise except: self.handleError(record) @@ -1356,6 +1426,16 @@ if threading: except queue.Empty: break + def enqueue_sentinel(self): + """ + This is used to enqueue the sentinel record. + + The base implementation uses put_nowait. You may want to override this + method if you want to use timeouts or work with custom queue + implementations. + """ + self.queue.put_nowait(self._sentinel) + def stop(self): """ Stop the listener. @@ -1365,6 +1445,6 @@ if threading: may be some records still left on the queue, which won't be processed. """ self._stop.set() - self.queue.put_nowait(self._sentinel) + self.enqueue_sentinel() self._thread.join() self._thread = None |