summaryrefslogtreecommitdiffstats
path: root/Lib/logging
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/logging')
-rw-r--r--Lib/logging/__init__.py613
-rw-r--r--Lib/logging/config.py704
-rw-r--r--Lib/logging/handlers.py287
3 files changed, 1293 insertions, 311 deletions
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 685efeb..e4b34a1 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2009 by Vinay Sajip. All Rights Reserved.
+# Copyright 2001-2010 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,
@@ -23,7 +23,8 @@ Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
-import sys, os, time, io, traceback, warnings
+import sys, os, time, io, traceback, warnings, weakref
+from string import Template
__all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO',
@@ -31,7 +32,8 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
'StreamHandler', 'WARN', 'WARNING', 'addLevelName', 'basicConfig',
'captureWarnings', 'critical', 'debug', 'disable', 'error',
'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass',
- 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning']
+ 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning',
+ 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort']
try:
import codecs
@@ -46,15 +48,13 @@ except ImportError:
__author__ = "Vinay Sajip <vinay_sajip@red-dove.com>"
__status__ = "production"
-__version__ = "0.5.0.7"
-__date__ = "20 January 2009"
+__version__ = "0.5.1.2"
+__date__ = "07 February 2010"
#---------------------------------------------------------------------------
# Miscellaneous module data
#---------------------------------------------------------------------------
-_unicode = 'unicode' in dir(__builtins__)
-
#
# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame.
@@ -176,19 +176,34 @@ def addLevelName(level, levelName):
finally:
_releaseLock()
+def _checkLevel(level):
+ if isinstance(level, int):
+ rv = level
+ elif str(level) == level:
+ if level not in _levelNames:
+ raise ValueError("Unknown level: %r" % level)
+ rv = _levelNames[level]
+ else:
+ raise TypeError("Level not an integer or a valid string: %r" % level)
+ return rv
+
#---------------------------------------------------------------------------
# Thread-related stuff
#---------------------------------------------------------------------------
#
#_lock is used to serialize access to shared data structures in this module.
-#This needs to be an RLock because fileConfig() creates Handlers and so
-#might arbitrary user threads. Since Handler.__init__() updates the shared
-#dictionary _handlers, it needs to acquire the lock. But if configuring,
+#This needs to be an RLock because fileConfig() creates and configures
+#Handlers, and so might arbitrary user threads. Since Handler code updates the
+#shared dictionary _handlers, it needs to acquire the lock. But if configuring,
#the lock would already have been acquired - so we need an RLock.
#The same argument applies to Loggers and Manager.loggerDict.
#
-_lock = None
+if thread:
+ _lock = threading.RLock()
+else:
+ _lock = None
+
def _acquireLock():
"""
@@ -196,9 +211,6 @@ def _acquireLock():
This should be released with _releaseLock().
"""
- global _lock
- if (not _lock) and thread:
- _lock = threading.RLock()
if _lock:
_lock.acquire()
@@ -213,7 +225,7 @@ def _releaseLock():
# The logging record
#---------------------------------------------------------------------------
-class LogRecord:
+class LogRecord(object):
"""
A LogRecord instance represents an event being logged.
@@ -226,7 +238,7 @@ class LogRecord:
information to be logged.
"""
def __init__(self, name, level, pathname, lineno,
- msg, args, exc_info, func=None):
+ msg, args, exc_info, func=None, sinfo=None, **kwargs):
"""
Initialize a logging record with interesting information.
"""
@@ -260,6 +272,7 @@ class LogRecord:
self.module = "Unknown module"
self.exc_info = exc_info
self.exc_text = None # used to cache the traceback text
+ self.stack_info = sinfo
self.lineno = lineno
self.funcName = func
self.created = ct
@@ -274,11 +287,17 @@ class LogRecord:
if not logMultiprocessing:
self.processName = None
else:
- try:
- from multiprocessing import current_process
- self.processName = current_process().name
- except ImportError:
- self.processName = None
+ self.processName = 'MainProcess'
+ mp = sys.modules.get('multiprocessing')
+ if mp is not None:
+ # Errors may occur if multiprocessing has not finished loading
+ # yet - e.g. if a custom import hook causes third-party code
+ # to run when multiprocessing calls import. See issue 8200
+ # for an example
+ try:
+ self.processName = mp.current_process().name
+ except StandardError:
+ pass
if logProcesses and hasattr(os, 'getpid'):
self.process = os.getpid()
else:
@@ -295,19 +314,33 @@ class LogRecord:
Return the message for this LogRecord after merging any user-supplied
arguments with the message.
"""
- if not _unicode: #if no unicode support...
- msg = str(self.msg)
- else:
- msg = self.msg
- if not isinstance(msg, str):
- try:
- msg = str(self.msg)
- except UnicodeError:
- msg = self.msg #Defer encoding till later
+ msg = str(self.msg)
if self.args:
msg = msg % self.args
return msg
+#
+# Determine which class to use when instantiating log records.
+#
+_logRecordFactory = LogRecord
+
+def setLogRecordFactory(factory):
+ """
+ Set the factory to be used when instantiating a log record.
+
+ :param factory: A callable which will be called to instantiate
+ a log record.
+ """
+ global _logRecordFactory
+ _logRecordFactory = factory
+
+def getLogRecordFactory():
+ """
+ Return the factory to be used when instantiating a log record.
+ """
+
+ return _logRecordFactory
+
def makeLogRecord(dict):
"""
Make a LogRecord whose attributes are defined by the specified dictionary,
@@ -315,7 +348,7 @@ def makeLogRecord(dict):
a socket connection (which is sent as a dictionary) into a LogRecord
instance.
"""
- rv = LogRecord(None, None, "", 0, "", (), None, None)
+ rv = _logRecordFactory(None, None, "", 0, "", (), None, None)
rv.__dict__.update(dict)
return rv
@@ -323,7 +356,53 @@ def makeLogRecord(dict):
# Formatter classes and functions
#---------------------------------------------------------------------------
-class Formatter:
+class PercentStyle(object):
+
+ default_format = '%(message)s'
+ asctime_format = '%(asctime)s'
+ asctime_search = '%(asctime)'
+
+ def __init__(self, fmt):
+ self._fmt = fmt or self.default_format
+
+ def usesTime(self):
+ return self._fmt.find(self.asctime_search) >= 0
+
+ def format(self, record):
+ return self._fmt % record.__dict__
+
+class StrFormatStyle(PercentStyle):
+ default_format = '{message}'
+ asctime_format = '{asctime}'
+ asctime_search = '{asctime'
+
+ def format(self, record):
+ return self._fmt.format(**record.__dict__)
+
+
+class StringTemplateStyle(PercentStyle):
+ default_format = '${message}'
+ asctime_format = '${asctime}'
+ asctime_search = '${asctime}'
+
+ def __init__(self, fmt):
+ self._fmt = fmt or self.default_format
+ self._tpl = Template(self._fmt)
+
+ def usesTime(self):
+ fmt = self._fmt
+ return fmt.find('$asctime') >= 0 or fmt.find(self.asctime_format) >= 0
+
+ def format(self, record):
+ return self._tpl.substitute(**record.__dict__)
+
+_STYLES = {
+ '%': PercentStyle,
+ '{': StrFormatStyle,
+ '$': StringTemplateStyle
+}
+
+class Formatter(object):
"""
Formatter instances are used to convert a LogRecord to text.
@@ -331,7 +410,7 @@ class Formatter:
responsible for converting a LogRecord to (usually) a string which can
be interpreted by either a human or an external system. The base Formatter
allows a formatting string to be specified. If none is supplied, the
- default value of "%s(message)\\n" is used.
+ default value of "%s(message)" is used.
The Formatter can be initialized with a format string which makes use of
knowledge of the LogRecord attributes - e.g. the default value mentioned
@@ -367,18 +446,26 @@ class Formatter:
converter = time.localtime
- def __init__(self, fmt=None, datefmt=None):
+ def __init__(self, fmt=None, datefmt=None, style='%'):
"""
Initialize the formatter with specified format strings.
Initialize the formatter either with the specified format string, or a
default as described above. Allow for specialized date formatting with
the optional datefmt argument (if omitted, you get the ISO8601 format).
+
+ Use a style parameter of '%', '{' or '$' to specify that you want to
+ use one of %-formatting, :meth:`str.format` (``{}``) formatting or
+ :class:`string.Template` formatting in your format string.
+
+ .. versionchanged: 3.2
+ Added the ``style`` parameter.
"""
- if fmt:
- self._fmt = fmt
- else:
- self._fmt = "%(message)s"
+ if style not in _STYLES:
+ raise ValueError('Style must be one of: %s' % ','.join(
+ _STYLES.keys()))
+ self._style = _STYLES[style](fmt)
+ self._fmt = self._style._fmt
self.datefmt = datefmt
def formatTime(self, record, datefmt=None):
@@ -404,7 +491,7 @@ class Formatter:
s = time.strftime(datefmt, ct)
else:
t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
- s = "%s,%03d" % (t, record.msecs)
+ s = "%s,%03d" % (t, record.msecs) # the use of % here is internal
return s
def formatException(self, ei):
@@ -415,13 +502,39 @@ class Formatter:
traceback.print_exception()
"""
sio = io.StringIO()
- traceback.print_exception(ei[0], ei[1], ei[2], None, sio)
+ tb = ei[2]
+ # See issues #9427, #1553375. Commented out for now.
+ #if getattr(self, 'fullstack', False):
+ # traceback.print_stack(tb.tb_frame.f_back, file=sio)
+ traceback.print_exception(ei[0], ei[1], tb, None, sio)
s = sio.getvalue()
sio.close()
if s[-1:] == "\n":
s = s[:-1]
return s
+ def usesTime(self):
+ """
+ Check if the format uses the creation time of the record.
+ """
+ return self._style.usesTime()
+
+ def formatMessage(self, record):
+ return self._style.format(record)
+
+ def formatStack(self, stack_info):
+ """
+ This method is provided as an extension point for specialized
+ formatting of stack information.
+
+ The input data is a string as returned from a call to
+ :func:`traceback.print_stack`, but with the last trailing newline
+ removed.
+
+ The base implementation just returns the value passed in.
+ """
+ return stack_info
+
def format(self, record):
"""
Format the specified record as text.
@@ -430,15 +543,15 @@ class Formatter:
string formatting operation which yields the returned string.
Before formatting the dictionary, a couple of preparatory steps
are carried out. The message attribute of the record is computed
- using LogRecord.getMessage(). If the formatting string contains
- "%(asctime)", formatTime() is called to format the event time.
- If there is exception information, it is formatted using
- formatException() and appended to the message.
+ using LogRecord.getMessage(). If the formatting string uses the
+ time (as determined by a call to usesTime(), formatTime() is
+ called to format the event time. If there is exception information,
+ it is formatted using formatException() and appended to the message.
"""
record.message = record.getMessage()
- if self._fmt.find("%(asctime)") >= 0:
+ if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
- s = self._fmt % record.__dict__
+ s = self.formatMessage(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
@@ -448,6 +561,10 @@ class Formatter:
if s[-1:] != "\n":
s = s + "\n"
s = s + record.exc_text
+ if record.stack_info:
+ if s[-1:] != "\n":
+ s = s + "\n"
+ s = s + self.formatStack(record.stack_info)
return s
#
@@ -455,7 +572,7 @@ class Formatter:
#
_defaultFormatter = Formatter()
-class BufferingFormatter:
+class BufferingFormatter(object):
"""
A formatter suitable for formatting a number of records.
"""
@@ -497,7 +614,7 @@ class BufferingFormatter:
# Filter classes and functions
#---------------------------------------------------------------------------
-class Filter:
+class Filter(object):
"""
Filter instances are used to perform arbitrary filtering of LogRecords.
@@ -534,7 +651,7 @@ class Filter:
return 0
return (record.name[self.nlen] == ".")
-class Filterer:
+class Filterer(object):
"""
A base class for loggers and handlers which allows them to share
common code.
@@ -566,10 +683,18 @@ class Filterer:
The default is to allow the record to be logged; any filter can veto
this and the record is then dropped. Returns a zero value if a record
is to be dropped, else non-zero.
+
+ .. versionchanged: 3.2
+
+ Allow filters to be just callables.
"""
rv = 1
for f in self.filters:
- if not f.filter(record):
+ if hasattr(f, 'filter'):
+ result = f.filter(record)
+ else:
+ result = f(record) # assume callable - will raise if not
+ if not result:
rv = 0
break
return rv
@@ -578,9 +703,34 @@ class Filterer:
# Handler classes and functions
#---------------------------------------------------------------------------
-_handlers = {} #repository of handlers (for flushing when shutdown called)
+_handlers = weakref.WeakValueDictionary() #map of handler names to handlers
_handlerList = [] # added to allow handlers to be removed in reverse of order initialized
+def _removeHandlerRef(wr):
+ """
+ Remove a handler reference from the internal cleanup list.
+ """
+ # This function can be called during module teardown, when globals are
+ # set to None. If _acquireLock is None, assume this is the case and do
+ # nothing.
+ if _acquireLock is not None:
+ _acquireLock()
+ try:
+ if wr in _handlerList:
+ _handlerList.remove(wr)
+ finally:
+ _releaseLock()
+
+def _addHandlerRef(handler):
+ """
+ Add a handler to the internal cleanup list using a weak reference.
+ """
+ _acquireLock()
+ try:
+ _handlerList.append(weakref.ref(handler, _removeHandlerRef))
+ finally:
+ _releaseLock()
+
class Handler(Filterer):
"""
Handler instances dispatch logging events to specific destinations.
@@ -596,16 +746,28 @@ class Handler(Filterer):
and the filter list to empty.
"""
Filterer.__init__(self)
- self.level = level
+ self._name = None
+ self.level = _checkLevel(level)
self.formatter = None
- #get the module data lock, as we're updating a shared structure.
+ # Add the handler to the global _handlerList (for cleanup on shutdown)
+ _addHandlerRef(self)
+ self.createLock()
+
+ def get_name(self):
+ return self._name
+
+ def set_name(self, name):
_acquireLock()
- try: #unlikely to raise an exception, but you never know...
- _handlers[self] = 1
- _handlerList.insert(0, self)
+ try:
+ if self._name in _handlers:
+ del _handlers[self._name]
+ self._name = name
+ if name:
+ _handlers[name] = self
finally:
_releaseLock()
- self.createLock()
+
+ name = property(get_name, set_name)
def createLock(self):
"""
@@ -634,7 +796,7 @@ class Handler(Filterer):
"""
Set the logging level of this handler.
"""
- self.level = level
+ self.level = _checkLevel(level)
def format(self, record):
"""
@@ -696,16 +858,16 @@ class Handler(Filterer):
"""
Tidy up any resources used by the handler.
- This version does removes the handler from an internal list
- of handlers which is closed when shutdown() is called. Subclasses
+ This version removes the handler from an internal map of handlers,
+ _handlers, which is used for handler lookup by name. Subclasses
should ensure that this gets called from overridden close()
methods.
"""
#get the module data lock, as we're updating a shared structure.
_acquireLock()
try: #unlikely to raise an exception, but you never know...
- del _handlers[self]
- _handlerList.remove(self)
+ if self._name and self._name in _handlers:
+ del _handlers[self._name]
finally:
_releaseLock()
@@ -724,7 +886,10 @@ class Handler(Filterer):
if raiseExceptions:
ei = sys.exc_info()
try:
- traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
+ traceback.print_exception(ei[0], ei[1], ei[2],
+ None, sys.stderr)
+ sys.stderr.write('Logged from file %s, line %s\n' % (
+ record.filename, record.lineno))
except IOError:
pass # see issue 5971
finally:
@@ -737,6 +902,8 @@ class StreamHandler(Handler):
sys.stdout or sys.stderr may be used.
"""
+ terminator = '\n'
+
def __init__(self, stream=None):
"""
Initialize the handler.
@@ -769,28 +936,8 @@ class StreamHandler(Handler):
try:
msg = self.format(record)
stream = self.stream
- fs = "%s\n"
- if not _unicode: #if no unicode support...
- stream.write(fs % msg)
- else:
- try:
- if (isinstance(msg, unicode) and
- getattr(stream, 'encoding', None)):
- fs = fs.decode(stream.encoding)
- try:
- stream.write(fs % msg)
- except UnicodeEncodeError:
- #Printing to terminals sometimes fails. For example,
- #with an encoding of 'cp1251', the above write will
- #work if written to a stream opened or wrapped by
- #the codecs module, but fail when writing to a
- #terminal even when the codepage is set to cp1251.
- #An extra encoding step seems to be needed.
- stream.write((fs % msg).encode(stream.encoding))
- else:
- stream.write(fs % msg)
- except UnicodeError:
- stream.write(fs % msg.encode("UTF-8"))
+ stream.write(msg)
+ stream.write(self.terminator)
self.flush()
except (KeyboardInterrupt, SystemExit):
raise
@@ -853,11 +1000,31 @@ class FileHandler(StreamHandler):
self.stream = self._open()
StreamHandler.emit(self, record)
+class _StderrHandler(StreamHandler):
+ """
+ This class is like a StreamHandler using sys.stderr, but always uses
+ whatever sys.stderr is currently set to rather than the value of
+ sys.stderr at handler construction time.
+ """
+ def __init__(self, level=NOTSET):
+ """
+ Initialize the handler.
+ """
+ Handler.__init__(self, level)
+
+ @property
+ def stream(self):
+ return sys.stderr
+
+
+_defaultLastResort = _StderrHandler(WARNING)
+lastResort = _defaultLastResort
+
#---------------------------------------------------------------------------
# Manager classes and functions
#---------------------------------------------------------------------------
-class PlaceHolder:
+class PlaceHolder(object):
"""
PlaceHolder instances are used in the Manager logger hierarchy to take
the place of nodes for which no loggers have been defined. This class is
@@ -867,16 +1034,13 @@ class PlaceHolder:
"""
Initialize with the specified logger being a child of this placeholder.
"""
- #self.loggers = [alogger]
self.loggerMap = { alogger : None }
def append(self, alogger):
"""
Add the specified logger as a child of this placeholder.
"""
- #if alogger not in self.loggers:
if alogger not in self.loggerMap:
- #self.loggers.append(alogger)
self.loggerMap[alogger] = None
#
@@ -904,7 +1068,7 @@ def getLoggerClass():
return _loggerClass
-class Manager:
+class Manager(object):
"""
There is [under normal circumstances] just one Manager instance, which
holds the hierarchy of loggers.
@@ -915,8 +1079,10 @@ class Manager:
"""
self.root = rootnode
self.disable = 0
- self.emittedNoHandlerWarning = 0
+ self.emittedNoHandlerWarning = False
self.loggerDict = {}
+ self.loggerClass = None
+ self.logRecordFactory = None
def getLogger(self, name):
"""
@@ -936,13 +1102,13 @@ class Manager:
rv = self.loggerDict[name]
if isinstance(rv, PlaceHolder):
ph = rv
- rv = _loggerClass(name)
+ rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupChildren(ph, rv)
self._fixupParents(rv)
else:
- rv = _loggerClass(name)
+ rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupParents(rv)
@@ -950,6 +1116,23 @@ class Manager:
_releaseLock()
return rv
+ def setLoggerClass(self, klass):
+ """
+ Set the class to be used when instantiating a logger with this Manager.
+ """
+ if klass != Logger:
+ if not issubclass(klass, Logger):
+ raise TypeError("logger not derived from logging.Logger: "
+ + klass.__name__)
+ self.loggerClass = klass
+
+ def setLogRecordFactory(self, factory):
+ """
+ Set the factory to be used when instantiating a log record with this
+ Manager.
+ """
+ self.logRecordFactory = factory
+
def _fixupParents(self, alogger):
"""
Ensure that there are either loggers or placeholders all the way
@@ -1012,7 +1195,7 @@ class Logger(Filterer):
"""
Filterer.__init__(self)
self.name = name
- self.level = level
+ self.level = _checkLevel(level)
self.parent = None
self.propagate = 1
self.handlers = []
@@ -1022,7 +1205,7 @@ class Logger(Filterer):
"""
Set the logging level of this logger.
"""
- self.level = level
+ self.level = _checkLevel(level)
def debug(self, msg, *args, **kwargs):
"""
@@ -1074,11 +1257,12 @@ class Logger(Filterer):
if self.isEnabledFor(ERROR):
self._log(ERROR, msg, args, **kwargs)
- def exception(self, msg, *args):
+ def exception(self, msg, *args, **kwargs):
"""
Convenience method for logging an ERROR with exception information.
"""
- self.error(msg, exc_info=1, *args)
+ kwargs['exc_info'] = True
+ self.error(msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
"""
@@ -1111,7 +1295,7 @@ class Logger(Filterer):
if self.isEnabledFor(level):
self._log(level, msg, args, **kwargs)
- def findCaller(self):
+ def findCaller(self, stack_info=False):
"""
Find the stack frame of the caller so that we can note the source
file name, line number and function name.
@@ -1121,23 +1305,34 @@ class Logger(Filterer):
#IronPython isn't run with -X:Frames.
if f is not None:
f = f.f_back
- rv = "(unknown file)", 0, "(unknown function)"
+ rv = "(unknown file)", 0, "(unknown function)", None
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename == _srcfile:
f = f.f_back
continue
- rv = (filename, f.f_lineno, co.co_name)
+ sinfo = None
+ if stack_info:
+ sio = io.StringIO()
+ sio.write('Stack (most recent call last):\n')
+ traceback.print_stack(f, file=sio)
+ sinfo = sio.getvalue()
+ if sinfo[-1] == '\n':
+ sinfo = sinfo[:-1]
+ sio.close()
+ rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
break
return rv
- def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
+ def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
+ func=None, extra=None, sinfo=None):
"""
A factory method which can be overridden in subclasses to create
specialized LogRecords.
"""
- rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func)
+ rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
+ sinfo)
if extra is not None:
for key in extra:
if (key in ["message", "asctime"]) or (key in rv.__dict__):
@@ -1145,17 +1340,18 @@ class Logger(Filterer):
rv.__dict__[key] = extra[key]
return rv
- def _log(self, level, msg, args, exc_info=None, extra=None):
+ def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
"""
Low-level logging routine which creates a LogRecord and then calls
all the handlers of this logger to handle the record.
"""
+ sinfo = None
if _srcfile:
#IronPython doesn't track Python frames, so findCaller throws an
#exception on some versions of IronPython. We trap it here so that
#IronPython can use logging.
try:
- fn, lno, func = self.findCaller()
+ fn, lno, func, sinfo = self.findCaller(stack_info)
except ValueError:
fn, lno, func = "(unknown file)", 0, "(unknown function)"
else:
@@ -1163,7 +1359,8 @@ class Logger(Filterer):
if exc_info:
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)
+ record = self.makeRecord(self.name, level, fn, lno, msg, args,
+ exc_info, func, extra, sinfo)
self.handle(record)
def handle(self, record):
@@ -1198,6 +1395,28 @@ class Logger(Filterer):
finally:
_releaseLock()
+ def hasHandlers(self):
+ """
+ See if this logger has any handlers configured.
+
+ Loop through all handlers for this logger and its parents in the
+ logger hierarchy. Return True if a handler was found, else False.
+ Stop searching up the hierarchy whenever a logger with the "propagate"
+ attribute set to zero is found - that will be the last logger which
+ is checked for the existence of handlers.
+ """
+ c = self
+ rv = False
+ while c:
+ if c.handlers:
+ rv = True
+ break
+ if not c.propagate:
+ break
+ else:
+ c = c.parent
+ return rv
+
def callHandlers(self, record):
"""
Pass a record to all relevant handlers.
@@ -1219,10 +1438,13 @@ class Logger(Filterer):
c = None #break out
else:
c = c.parent
- if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning:
- sys.stderr.write("No handlers could be found for logger"
- " \"%s\"\n" % self.name)
- self.manager.emittedNoHandlerWarning = 1
+ if (found == 0):
+ if lastResort:
+ lastResort.handle(record)
+ elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
+ sys.stderr.write("No handlers could be found for logger"
+ " \"%s\"\n" % self.name)
+ self.manager.emittedNoHandlerWarning = True
def getEffectiveLevel(self):
"""
@@ -1246,6 +1468,25 @@ class Logger(Filterer):
return 0
return level >= self.getEffectiveLevel()
+ def getChild(self, suffix):
+ """
+ Get a logger which is a descendant to this one.
+
+ This is a convenience method, such that
+
+ logging.getLogger('abc').getChild('def.ghi')
+
+ is the same as
+
+ logging.getLogger('abc.def.ghi')
+
+ It's useful, for example, when the parent logger is named using
+ __name__ rather than a literal string.
+ """
+ if self.root is not self:
+ suffix = '.'.join((self.name, suffix))
+ return self.manager.getLogger(suffix)
+
class RootLogger(Logger):
"""
A root logger is not that different to any other logger, except that
@@ -1260,7 +1501,7 @@ class RootLogger(Logger):
_loggerClass = Logger
-class LoggerAdapter:
+class LoggerAdapter(object):
"""
An adapter for loggers which makes it easier to specify contextual
information in logging output.
@@ -1293,62 +1534,82 @@ class LoggerAdapter:
kwargs["extra"] = self.extra
return msg, kwargs
+ #
+ # Boilerplate convenience methods
+ #
def debug(self, msg, *args, **kwargs):
"""
- Delegate a debug call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate a debug call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.debug(msg, *args, **kwargs)
+ self.log(DEBUG, msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
"""
- Delegate an info call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate an info call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.info(msg, *args, **kwargs)
+ self.log(INFO, msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
"""
- Delegate a warning call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate a warning call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.warning(msg, *args, **kwargs)
+ self.log(WARNING, msg, *args, **kwargs)
+
+ warn = warning
def error(self, msg, *args, **kwargs):
"""
- Delegate an error call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate an error call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.error(msg, *args, **kwargs)
+ self.log(ERROR, msg, *args, **kwargs)
def exception(self, msg, *args, **kwargs):
"""
- Delegate an exception call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate an exception call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
kwargs["exc_info"] = 1
- self.logger.error(msg, *args, **kwargs)
+ self.log(ERROR, msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
"""
- Delegate a critical call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate a critical call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.critical(msg, *args, **kwargs)
+ self.log(CRITICAL, msg, *args, **kwargs)
def log(self, level, msg, *args, **kwargs):
"""
Delegate a log call to the underlying logger, after adding
contextual information from this adapter instance.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.log(level, msg, *args, **kwargs)
+ if self.isEnabledFor(level):
+ msg, kwargs = self.process(msg, kwargs)
+ self.logger._log(level, msg, args, **kwargs)
+
+ def isEnabledFor(self, level):
+ """
+ Is this logger enabled for level 'level'?
+ """
+ if self.logger.manager.disable >= level:
+ return False
+ return level >= self.getEffectiveLevel()
+
+ def setLevel(self, level):
+ """
+ Set the specified level on the underlying logger.
+ """
+ self.logger.setLevel(level)
+
+ def getEffectiveLevel(self):
+ """
+ Get the effective level for the underlying logger.
+ """
+ return self.logger.getEffectiveLevel()
+
+ def hasHandlers(self):
+ """
+ See if the underlying logger has any handlers.
+ """
+ return self.logger.hasHandlers()
root = RootLogger(WARNING)
Logger.root = root
@@ -1381,6 +1642,10 @@ def basicConfig(**kwargs):
(if filemode is unspecified, it defaults to 'a').
format Use the specified format string for the handler.
datefmt Use the specified date/time format.
+ style If a format string is specified, use this to specify the
+ type of format string (possible values '%', '{', '$', for
+ %-formatting, :meth:`str.format` and :class:`string.Template`
+ - defaults to '%').
level Set the root logger level to the specified level.
stream Use the specified stream to initialize the StreamHandler. Note
that this argument is incompatible with 'filename' - if both
@@ -1391,6 +1656,9 @@ def basicConfig(**kwargs):
remembered that StreamHandler does not close its stream (since it may be
using sys.stdout or sys.stderr), whereas FileHandler closes its stream
when the handler is closed.
+
+ .. versionchanged: 3.2
+ Added the ``style`` parameter.
"""
# Add thread safety in case someone mistakenly calls
# basicConfig() from multiple threads
@@ -1406,7 +1674,8 @@ def basicConfig(**kwargs):
hdlr = StreamHandler(stream)
fs = kwargs.get("format", BASIC_FORMAT)
dfs = kwargs.get("datefmt", None)
- fmt = Formatter(fs, dfs)
+ style = kwargs.get("style", '%')
+ fmt = Formatter(fs, dfs, style)
hdlr.setFormatter(fmt)
root.addHandler(hdlr)
level = kwargs.get("level")
@@ -1433,7 +1702,9 @@ def getLogger(name=None):
def critical(msg, *args, **kwargs):
"""
- Log a message with severity 'CRITICAL' on the root logger.
+ Log a message with severity 'CRITICAL' on the root logger. If the logger
+ has no handlers, call basicConfig() to add a console handler with a
+ pre-defined format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1443,22 +1714,28 @@ fatal = critical
def error(msg, *args, **kwargs):
"""
- Log a message with severity 'ERROR' on the root logger.
+ Log a message with severity 'ERROR' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
root.error(msg, *args, **kwargs)
-def exception(msg, *args):
+def exception(msg, *args, **kwargs):
"""
- Log a message with severity 'ERROR' on the root logger,
- with exception information.
+ Log a message with severity 'ERROR' on the root logger, with exception
+ information. If the logger has no handlers, basicConfig() is called to add
+ a console handler with a pre-defined format.
"""
- error(msg, exc_info=1, *args)
+ kwargs['exc_info'] = True
+ error(msg, *args, **kwargs)
def warning(msg, *args, **kwargs):
"""
- Log a message with severity 'WARNING' on the root logger.
+ Log a message with severity 'WARNING' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1468,7 +1745,9 @@ warn = warning
def info(msg, *args, **kwargs):
"""
- Log a message with severity 'INFO' on the root logger.
+ Log a message with severity 'INFO' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1476,7 +1755,9 @@ def info(msg, *args, **kwargs):
def debug(msg, *args, **kwargs):
"""
- Log a message with severity 'DEBUG' on the root logger.
+ Log a message with severity 'DEBUG' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1484,7 +1765,9 @@ def debug(msg, *args, **kwargs):
def log(level, msg, *args, **kwargs):
"""
- Log 'msg % args' with the integer severity 'level' on the root logger.
+ Log 'msg % args' with the integer severity 'level' on the root logger. If
+ the logger has no handlers, call basicConfig() to add a console handler
+ with a pre-defined format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1492,7 +1775,7 @@ def log(level, msg, *args, **kwargs):
def disable(level):
"""
- Disable all logging calls less severe than 'level'.
+ Disable all logging calls of severity 'level' and below.
"""
root.manager.disable = level
@@ -1503,32 +1786,32 @@ def shutdown(handlerList=_handlerList):
Should be called at application exit.
"""
- for h in handlerList[:]:
+ for wr in reversed(handlerList[:]):
#errors might occur, for example, if files are locked
#we just ignore them if raiseExceptions is not set
try:
- h.acquire()
- h.flush()
- h.close()
+ h = wr()
+ if h:
+ try:
+ h.acquire()
+ h.flush()
+ h.close()
+ except (IOError, ValueError):
+ # Ignore errors which might be caused
+ # because handlers have been closed but
+ # references to them are still around at
+ # application exit.
+ pass
+ finally:
+ h.release()
except:
if raiseExceptions:
raise
#else, swallow
- finally:
- h.release()
#Let's try and shutdown automatically on application exit...
-try:
- import atexit
- atexit.register(shutdown)
-except ImportError: # for Python versions < 2.0
- def exithook(status, old_exit=sys.exit):
- try:
- shutdown()
- finally:
- old_exit(status)
-
- sys.exit = exithook
+import atexit
+atexit.register(shutdown)
# Null handler
diff --git a/Lib/logging/config.py b/Lib/logging/config.py
index 25f34ec..c7359ca 100644
--- a/Lib/logging/config.py
+++ b/Lib/logging/config.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved.
+# Copyright 2001-2010 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,
@@ -24,7 +24,8 @@ Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
-import sys, logging, logging.handlers, socket, struct, os, traceback
+import sys, logging, logging.handlers, socket, struct, os, traceback, re
+import types, io
try:
import _thread as thread
@@ -57,16 +58,12 @@ def fileConfig(fname, defaults=None, disable_existing_loggers=True):
the ability to select from various pre-canned configurations (if the
developer provides a mechanism to present the choices and load the chosen
configuration).
- In versions of ConfigParser which have the readfp method [typically
- shipped in 2.x versions of Python], you can pass in a file-like object
- rather than a filename, in which case the file-like object will be read
- using readfp.
"""
import configparser
cp = configparser.ConfigParser(defaults)
- if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
- cp.readfp(fname)
+ if hasattr(fname, 'readline'):
+ cp.read_file(fname)
else:
cp.read(fname)
@@ -101,9 +98,12 @@ 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.get("formatters", "keys")
+ flist = cp["formatters"]["keys"]
if not len(flist):
return {}
flist = flist.split(",")
@@ -111,20 +111,12 @@ def _create_formatters(cp):
formatters = {}
for form in flist:
sectname = "formatter_%s" % form
- opts = cp.options(sectname)
- if "format" in opts:
- fs = cp.get(sectname, "format", 1)
- else:
- fs = None
- if "datefmt" in opts:
- dfs = cp.get(sectname, "datefmt", 1)
- else:
- dfs = None
+ fs = cp.get(sectname, "format", raw=True, fallback=None)
+ dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
c = logging.Formatter
- if "class" in opts:
- class_name = cp.get(sectname, "class")
- if class_name:
- c = _resolve(class_name)
+ class_name = cp[sectname].get("class")
+ if class_name:
+ c = _resolve(class_name)
f = c(fs, dfs)
formatters[form] = f
return formatters
@@ -132,7 +124,7 @@ def _create_formatters(cp):
def _install_handlers(cp, formatters):
"""Install and return handlers"""
- hlist = cp.get("handlers", "keys")
+ hlist = cp["handlers"]["keys"]
if not len(hlist):
return {}
hlist = hlist.split(",")
@@ -140,30 +132,23 @@ def _install_handlers(cp, formatters):
handlers = {}
fixups = [] #for inter-handler references
for hand in hlist:
- sectname = "handler_%s" % hand
- klass = cp.get(sectname, "class")
- opts = cp.options(sectname)
- if "formatter" in opts:
- fmt = cp.get(sectname, "formatter")
- else:
- fmt = ""
+ section = cp["handler_%s" % hand]
+ klass = section["class"]
+ fmt = section.get("formatter", "")
try:
klass = eval(klass, vars(logging))
except (AttributeError, NameError):
klass = _resolve(klass)
- args = cp.get(sectname, "args")
+ args = section["args"]
args = eval(args, vars(logging))
h = klass(*args)
- if "level" in opts:
- level = cp.get(sectname, "level")
+ if "level" in section:
+ level = section["level"]
h.setLevel(logging._levelNames[level])
if len(fmt):
h.setFormatter(formatters[fmt])
if issubclass(klass, logging.handlers.MemoryHandler):
- if "target" in opts:
- target = cp.get(sectname,"target")
- else:
- target = ""
+ target = section.get("target", "")
if len(target): #the target handler may not be loaded yet, so keep for later...
fixups.append((h, target))
handlers[hand] = h
@@ -172,25 +157,44 @@ def _install_handlers(cp, formatters):
h.setTarget(handlers[t])
return handlers
+def _handle_existing_loggers(existing, child_loggers, disable_existing):
+ """
+ When (re)configuring logging, handle loggers which were in the previous
+ configuration but are not in the new configuration. There's no point
+ deleting them as other threads may continue to hold references to them;
+ and by disabling them, you stop them doing any logging.
+
+ However, don't disable children of named loggers, as that's probably not
+ what was intended by the user. Also, allow existing loggers to NOT be
+ disabled if disable_existing is false.
+ """
+ root = logging.root
+ for log in existing:
+ logger = root.manager.loggerDict[log]
+ if log in child_loggers:
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.propagate = True
+ elif disable_existing:
+ logger.disabled = True
-def _install_loggers(cp, handlers, disable_existing_loggers):
+def _install_loggers(cp, handlers, disable_existing):
"""Create and install loggers"""
# configure the root first
- llist = cp.get("loggers", "keys")
+ llist = cp["loggers"]["keys"]
llist = llist.split(",")
llist = list(map(lambda x: x.strip(), llist))
llist.remove("root")
- sectname = "logger_root"
+ section = cp["logger_root"]
root = logging.root
log = root
- opts = cp.options(sectname)
- if "level" in opts:
- level = cp.get(sectname, "level")
+ if "level" in section:
+ level = section["level"]
log.setLevel(logging._levelNames[level])
for h in root.handlers[:]:
root.removeHandler(h)
- hlist = cp.get(sectname, "handlers")
+ hlist = section["handlers"]
if len(hlist):
hlist = hlist.split(",")
hlist = _strip_spaces(hlist)
@@ -211,19 +215,15 @@ def _install_loggers(cp, handlers, disable_existing_loggers):
#avoid disabling child loggers of explicitly
#named loggers. With a sorted list it is easier
#to find the child loggers.
- existing.sort()
+ existing.sort(key=_encoded)
#We'll keep the list of existing loggers
#which are children of named loggers here...
child_loggers = []
#now set up the new ones...
for log in llist:
- sectname = "logger_%s" % log
- qn = cp.get(sectname, "qualname")
- opts = cp.options(sectname)
- if "propagate" in opts:
- propagate = cp.getint(sectname, "propagate")
- else:
- propagate = 1
+ section = cp["logger_%s" % log]
+ qn = section["qualname"]
+ propagate = section.getint("propagate", fallback=1)
logger = logging.getLogger(qn)
if qn in existing:
i = existing.index(qn) + 1 # start with the entry after qn
@@ -235,14 +235,14 @@ def _install_loggers(cp, handlers, disable_existing_loggers):
child_loggers.append(existing[i])
i += 1
existing.remove(qn)
- if "level" in opts:
- level = cp.get(sectname, "level")
+ if "level" in section:
+ level = section["level"]
logger.setLevel(logging._levelNames[level])
for h in logger.handlers[:]:
logger.removeHandler(h)
logger.propagate = propagate
logger.disabled = 0
- hlist = cp.get(sectname, "handlers")
+ hlist = section["handlers"]
if len(hlist):
hlist = hlist.split(",")
hlist = _strip_spaces(hlist)
@@ -254,14 +254,526 @@ def _install_loggers(cp, handlers, disable_existing_loggers):
#and by disabling them, you stop them doing any logging.
#However, don't disable children of named loggers, as that's
#probably not what was intended by the user.
- for log in existing:
- logger = root.manager.loggerDict[log]
- if log in child_loggers:
- logger.level = logging.NOTSET
- logger.handlers = []
- logger.propagate = 1
- elif disable_existing_loggers:
- logger.disabled = 1
+ #for log in existing:
+ # logger = root.manager.loggerDict[log]
+ # if log in child_loggers:
+ # logger.level = logging.NOTSET
+ # logger.handlers = []
+ # logger.propagate = 1
+ # elif disable_existing_loggers:
+ # logger.disabled = 1
+ _handle_existing_loggers(existing, child_loggers, disable_existing)
+
+IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
+
+
+def valid_ident(s):
+ m = IDENTIFIER.match(s)
+ if not m:
+ raise ValueError('Not a valid Python identifier: %r' % s)
+ return True
+
+
+# The ConvertingXXX classes are wrappers around standard Python containers,
+# and they serve to convert any suitable values in the container. The
+# conversion converts base dicts, lists and tuples to their wrapped
+# equivalents, whereas strings which match a conversion format are converted
+# appropriately.
+#
+# Each wrapper should have a configurator attribute holding the actual
+# configurator to use for conversion.
+
+class ConvertingDict(dict):
+ """A converting dictionary wrapper."""
+
+ def __getitem__(self, key):
+ value = dict.__getitem__(self, key)
+ result = self.configurator.convert(value)
+ #If the converted value is different, save for next time
+ if value is not result:
+ self[key] = result
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+ def get(self, key, default=None):
+ value = dict.get(self, key, default)
+ result = self.configurator.convert(value)
+ #If the converted value is different, save for next time
+ if value is not result:
+ self[key] = result
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+ def pop(self, key, default=None):
+ value = dict.pop(self, key, default)
+ result = self.configurator.convert(value)
+ if value is not result:
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+class ConvertingList(list):
+ """A converting list wrapper."""
+ def __getitem__(self, key):
+ value = list.__getitem__(self, key)
+ result = self.configurator.convert(value)
+ #If the converted value is different, save for next time
+ if value is not result:
+ self[key] = result
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+ def pop(self, idx=-1):
+ value = list.pop(self, idx)
+ result = self.configurator.convert(value)
+ if value is not result:
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ return result
+
+class ConvertingTuple(tuple):
+ """A converting tuple wrapper."""
+ def __getitem__(self, key):
+ value = tuple.__getitem__(self, key)
+ result = self.configurator.convert(value)
+ if value is not result:
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+class BaseConfigurator(object):
+ """
+ The configurator base class which defines some useful defaults.
+ """
+
+ CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
+
+ WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
+ DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
+ INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
+ DIGIT_PATTERN = re.compile(r'^\d+$')
+
+ value_converters = {
+ 'ext' : 'ext_convert',
+ 'cfg' : 'cfg_convert',
+ }
+
+ # We might want to use a different one, e.g. importlib
+ importer = staticmethod(__import__)
+
+ def __init__(self, config):
+ self.config = ConvertingDict(config)
+ self.config.configurator = self
+
+ def resolve(self, s):
+ """
+ Resolve strings to objects using standard import and attribute
+ syntax.
+ """
+ name = s.split('.')
+ used = name.pop(0)
+ try:
+ found = self.importer(used)
+ for frag in name:
+ used += '.' + frag
+ try:
+ found = getattr(found, frag)
+ except AttributeError:
+ self.importer(used)
+ found = getattr(found, frag)
+ return found
+ except ImportError:
+ e, tb = sys.exc_info()[1:]
+ v = ValueError('Cannot resolve %r: %s' % (s, e))
+ v.__cause__, v.__traceback__ = e, tb
+ raise v
+
+ def ext_convert(self, value):
+ """Default converter for the ext:// protocol."""
+ return self.resolve(value)
+
+ def cfg_convert(self, value):
+ """Default converter for the cfg:// protocol."""
+ rest = value
+ m = self.WORD_PATTERN.match(rest)
+ if m is None:
+ raise ValueError("Unable to convert %r" % value)
+ else:
+ rest = rest[m.end():]
+ d = self.config[m.groups()[0]]
+ #print d, rest
+ while rest:
+ m = self.DOT_PATTERN.match(rest)
+ if m:
+ d = d[m.groups()[0]]
+ else:
+ m = self.INDEX_PATTERN.match(rest)
+ if m:
+ idx = m.groups()[0]
+ if not self.DIGIT_PATTERN.match(idx):
+ d = d[idx]
+ else:
+ try:
+ n = int(idx) # try as number first (most likely)
+ d = d[n]
+ except TypeError:
+ d = d[idx]
+ if m:
+ rest = rest[m.end():]
+ else:
+ raise ValueError('Unable to convert '
+ '%r at %r' % (value, rest))
+ #rest should be empty
+ return d
+
+ def convert(self, value):
+ """
+ Convert values to an appropriate type. dicts, lists and tuples are
+ replaced by their converting alternatives. Strings are checked to
+ see if they have a conversion format and are converted if they do.
+ """
+ if not isinstance(value, ConvertingDict) and isinstance(value, dict):
+ value = ConvertingDict(value)
+ value.configurator = self
+ elif not isinstance(value, ConvertingList) and isinstance(value, list):
+ value = ConvertingList(value)
+ value.configurator = self
+ elif not isinstance(value, ConvertingTuple) and\
+ isinstance(value, tuple):
+ value = ConvertingTuple(value)
+ value.configurator = self
+ elif isinstance(value, str): # str for py3k
+ m = self.CONVERT_PATTERN.match(value)
+ if m:
+ d = m.groupdict()
+ prefix = d['prefix']
+ converter = self.value_converters.get(prefix, None)
+ if converter:
+ suffix = d['suffix']
+ converter = getattr(self, converter)
+ value = converter(suffix)
+ return value
+
+ def configure_custom(self, config):
+ """Configure an object with a user-supplied factory."""
+ c = config.pop('()')
+ if not hasattr(c, '__call__'):
+ c = self.resolve(c)
+ props = config.pop('.', None)
+ # Check for valid identifiers
+ kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
+ result = c(**kwargs)
+ if props:
+ for name, value in props.items():
+ setattr(result, name, value)
+ return result
+
+ def as_tuple(self, value):
+ """Utility function which converts lists to tuples."""
+ if isinstance(value, list):
+ value = tuple(value)
+ return value
+
+class DictConfigurator(BaseConfigurator):
+ """
+ Configure logging using a dictionary-like object to describe the
+ configuration.
+ """
+
+ def configure(self):
+ """Do the configuration."""
+
+ config = self.config
+ if 'version' not in config:
+ raise ValueError("dictionary doesn't specify a version")
+ if config['version'] != 1:
+ raise ValueError("Unsupported version: %s" % config['version'])
+ incremental = config.pop('incremental', False)
+ EMPTY_DICT = {}
+ logging._acquireLock()
+ try:
+ if incremental:
+ handlers = config.get('handlers', EMPTY_DICT)
+ for name in handlers:
+ if name not in logging._handlers:
+ raise ValueError('No handler found with '
+ 'name %r' % name)
+ else:
+ try:
+ handler = logging._handlers[name]
+ handler_config = handlers[name]
+ level = handler_config.get('level', None)
+ if level:
+ handler.setLevel(logging._checkLevel(level))
+ except Exception as e:
+ raise ValueError('Unable to configure handler '
+ '%r: %s' % (name, e))
+ loggers = config.get('loggers', EMPTY_DICT)
+ for name in loggers:
+ try:
+ self.configure_logger(name, loggers[name], True)
+ except Exception as e:
+ raise ValueError('Unable to configure logger '
+ '%r: %s' % (name, e))
+ root = config.get('root', None)
+ if root:
+ try:
+ self.configure_root(root, True)
+ except Exception as e:
+ raise ValueError('Unable to configure root '
+ 'logger: %s' % e)
+ else:
+ disable_existing = config.pop('disable_existing_loggers', True)
+
+ logging._handlers.clear()
+ del logging._handlerList[:]
+
+ # Do formatters first - they don't refer to anything else
+ formatters = config.get('formatters', EMPTY_DICT)
+ for name in formatters:
+ try:
+ formatters[name] = self.configure_formatter(
+ formatters[name])
+ except Exception as e:
+ raise ValueError('Unable to configure '
+ 'formatter %r: %s' % (name, e))
+ # Next, do filters - they don't refer to anything else, either
+ filters = config.get('filters', EMPTY_DICT)
+ for name in filters:
+ try:
+ filters[name] = self.configure_filter(filters[name])
+ except Exception as e:
+ raise ValueError('Unable to configure '
+ 'filter %r: %s' % (name, e))
+
+ # Next, do handlers - they refer to formatters and filters
+ # As handlers can refer to other handlers, sort the keys
+ # to allow a deterministic order of configuration
+ handlers = config.get('handlers', EMPTY_DICT)
+ for name in sorted(handlers):
+ try:
+ handler = self.configure_handler(handlers[name])
+ handler.name = name
+ handlers[name] = handler
+ except Exception as e:
+ raise ValueError('Unable to configure handler '
+ '%r: %s' % (name, e))
+ # Next, do loggers - they refer to handlers and filters
+
+ #we don't want to lose the existing loggers,
+ #since other threads may have pointers to them.
+ #existing is set to contain all existing loggers,
+ #and as we go through the new configuration we
+ #remove any which are configured. At the end,
+ #what's left in existing is the set of loggers
+ #which were in the previous configuration but
+ #which are not in the new configuration.
+ root = logging.root
+ 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
+ #to find the child loggers.
+ existing.sort(key=_encoded)
+ #We'll keep the list of existing loggers
+ #which are children of named loggers here...
+ child_loggers = []
+ #now set up the new ones...
+ loggers = config.get('loggers', EMPTY_DICT)
+ for name in loggers:
+ if name in existing:
+ i = existing.index(name) + 1 # look after name
+ prefixed = name + "."
+ pflen = len(prefixed)
+ num_existing = len(existing)
+ while i < num_existing:
+ if existing[i][:pflen] == prefixed:
+ child_loggers.append(existing[i])
+ i += 1
+ existing.remove(name)
+ try:
+ self.configure_logger(name, loggers[name])
+ except Exception as e:
+ raise ValueError('Unable to configure logger '
+ '%r: %s' % (name, e))
+
+ #Disable any old loggers. There's no point deleting
+ #them as other threads may continue to hold references
+ #and by disabling them, you stop them doing any logging.
+ #However, don't disable children of named loggers, as that's
+ #probably not what was intended by the user.
+ #for log in existing:
+ # logger = root.manager.loggerDict[log]
+ # if log in child_loggers:
+ # logger.level = logging.NOTSET
+ # logger.handlers = []
+ # logger.propagate = True
+ # elif disable_existing:
+ # logger.disabled = True
+ _handle_existing_loggers(existing, child_loggers,
+ disable_existing)
+
+ # And finally, do the root logger
+ root = config.get('root', None)
+ if root:
+ try:
+ self.configure_root(root)
+ except Exception as e:
+ raise ValueError('Unable to configure root '
+ 'logger: %s' % e)
+ finally:
+ logging._releaseLock()
+
+ def configure_formatter(self, config):
+ """Configure a formatter from a dictionary."""
+ if '()' in config:
+ factory = config['()'] # for use in exception handler
+ try:
+ result = self.configure_custom(config)
+ except TypeError as te:
+ if "'format'" not in str(te):
+ raise
+ #Name of parameter changed from fmt to format.
+ #Retry with old name.
+ #This is so that code can be used with older Python versions
+ #(e.g. by Django)
+ config['fmt'] = config.pop('format')
+ config['()'] = factory
+ result = self.configure_custom(config)
+ else:
+ fmt = config.get('format', None)
+ dfmt = config.get('datefmt', None)
+ result = logging.Formatter(fmt, dfmt)
+ return result
+
+ def configure_filter(self, config):
+ """Configure a filter from a dictionary."""
+ if '()' in config:
+ result = self.configure_custom(config)
+ else:
+ name = config.get('name', '')
+ result = logging.Filter(name)
+ return result
+
+ def add_filters(self, filterer, filters):
+ """Add filters to a filterer from a list of names."""
+ for f in filters:
+ try:
+ filterer.addFilter(self.config['filters'][f])
+ except Exception as e:
+ raise ValueError('Unable to add filter %r: %s' % (f, e))
+
+ def configure_handler(self, config):
+ """Configure a handler from a dictionary."""
+ formatter = config.pop('formatter', None)
+ if formatter:
+ try:
+ formatter = self.config['formatters'][formatter]
+ except Exception as e:
+ raise ValueError('Unable to set formatter '
+ '%r: %s' % (formatter, e))
+ level = config.pop('level', None)
+ filters = config.pop('filters', None)
+ if '()' in config:
+ c = config.pop('()')
+ if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
+ c = self.resolve(c)
+ factory = c
+ else:
+ klass = self.resolve(config.pop('class'))
+ #Special case for handler which refers to another handler
+ if issubclass(klass, logging.handlers.MemoryHandler) and\
+ 'target' in config:
+ try:
+ config['target'] = self.config['handlers'][config['target']]
+ except Exception as e:
+ raise ValueError('Unable to set target handler '
+ '%r: %s' % (config['target'], e))
+ elif issubclass(klass, logging.handlers.SMTPHandler) and\
+ 'mailhost' in config:
+ config['mailhost'] = self.as_tuple(config['mailhost'])
+ elif issubclass(klass, logging.handlers.SysLogHandler) and\
+ 'address' in config:
+ config['address'] = self.as_tuple(config['address'])
+ factory = klass
+ kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
+ try:
+ result = factory(**kwargs)
+ except TypeError as te:
+ if "'stream'" not in str(te):
+ raise
+ #The argument name changed from strm to stream
+ #Retry with old name.
+ #This is so that code can be used with older Python versions
+ #(e.g. by Django)
+ kwargs['strm'] = kwargs.pop('stream')
+ result = factory(**kwargs)
+ if formatter:
+ result.setFormatter(formatter)
+ if level is not None:
+ result.setLevel(logging._checkLevel(level))
+ if filters:
+ self.add_filters(result, filters)
+ return result
+
+ def add_handlers(self, logger, handlers):
+ """Add handlers to a logger from a list of names."""
+ for h in handlers:
+ try:
+ logger.addHandler(self.config['handlers'][h])
+ except Exception as e:
+ raise ValueError('Unable to add handler %r: %s' % (h, e))
+
+ def common_logger_config(self, logger, config, incremental=False):
+ """
+ Perform configuration which is common to root and non-root loggers.
+ """
+ level = config.get('level', None)
+ if level is not None:
+ logger.setLevel(logging._checkLevel(level))
+ if not incremental:
+ #Remove any existing handlers
+ for h in logger.handlers[:]:
+ logger.removeHandler(h)
+ handlers = config.get('handlers', None)
+ if handlers:
+ self.add_handlers(logger, handlers)
+ filters = config.get('filters', None)
+ if filters:
+ self.add_filters(logger, filters)
+
+ def configure_logger(self, name, config, incremental=False):
+ """Configure a non-root logger from a dictionary."""
+ logger = logging.getLogger(name)
+ self.common_logger_config(logger, config, incremental)
+ propagate = config.get('propagate', None)
+ if propagate is not None:
+ logger.propagate = propagate
+
+ def configure_root(self, config, incremental=False):
+ """Configure a root logger from a dictionary."""
+ root = logging.getLogger()
+ self.common_logger_config(root, config, incremental)
+
+dictConfigClass = DictConfigurator
+
+def dictConfig(config):
+ """Configure logging using a dictionary."""
+ dictConfigClass(config).configure()
def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
@@ -301,24 +813,26 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + conn.recv(slen - len(chunk))
- #Apply new configuration. We'd like to be able to
- #create a StringIO and pass that in, but unfortunately
- #1.5.2 ConfigParser does not support reading file
- #objects, only actual files. So we create a temporary
- #file and remove it later.
- file = tempfile.mktemp(".ini")
- f = open(file, "w")
- f.write(chunk)
- f.close()
+ chunk = chunk.decode("utf-8")
try:
- fileConfig(file)
- except (KeyboardInterrupt, SystemExit):
- raise
+ import json
+ d =json.loads(chunk)
+ assert isinstance(d, dict)
+ dictConfig(d)
except:
- traceback.print_exc()
- os.remove(file)
+ #Apply new configuration.
+
+ file = io.StringIO(chunk)
+ try:
+ fileConfig(file)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ traceback.print_exc()
+ if self.server.ready:
+ self.server.ready.set()
except socket.error as e:
- if not isinstancetype(e.args, tuple):
+ if not isinstance(e.args, tuple):
raise
else:
errcode = e.args[0]
@@ -333,12 +847,13 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
allow_reuse_address = 1
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
- handler=None):
+ handler=None, ready=None):
ThreadingTCPServer.__init__(self, (host, port), handler)
logging._acquireLock()
self.abort = 0
logging._releaseLock()
self.timeout = 1
+ self.ready = ready
def serve_until_stopped(self):
import select
@@ -354,17 +869,28 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
logging._releaseLock()
self.socket.close()
- def serve(rcvr, hdlr, port):
- server = rcvr(port=port, handler=hdlr)
- global _listener
- logging._acquireLock()
- _listener = server
- logging._releaseLock()
- server.serve_until_stopped()
+ class Server(threading.Thread):
+
+ def __init__(self, rcvr, hdlr, port):
+ super(Server, self).__init__()
+ self.rcvr = rcvr
+ self.hdlr = hdlr
+ self.port = port
+ self.ready = threading.Event()
+
+ def run(self):
+ server = self.rcvr(port=self.port, handler=self.hdlr,
+ ready=self.ready)
+ if self.port == 0:
+ self.port = server.server_address[1]
+ self.ready.set()
+ global _listener
+ logging._acquireLock()
+ _listener = server
+ logging._releaseLock()
+ server.serve_until_stopped()
- return threading.Thread(target=serve,
- args=(ConfigSocketReceiver,
- ConfigStreamHandler, port))
+ return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
def stopListening():
"""
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index e9dac43..96384bd 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved.
+# Copyright 2001-2010 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,
@@ -19,7 +19,7 @@ Additional handlers 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.
-Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved.
+Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging.handlers' and log away!
"""
@@ -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
@@ -102,8 +103,13 @@ class RotatingFileHandler(BaseRotatingHandler):
If maxBytes is zero, rollover never occurs.
"""
+ # If rotation/rollover is wanted, it doesn't make sense to use another
+ # mode. If for example 'w' were specified, then if there were multiple
+ # runs of the calling application, the logs from previous runs would be
+ # lost if the 'w' is respected, because the log file would be truncated
+ # on each run.
if maxBytes > 0:
- mode = 'a' # doesn't make sense otherwise!
+ mode = 'a'
BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
self.maxBytes = maxBytes
self.backupCount = backupCount
@@ -120,7 +126,6 @@ class RotatingFileHandler(BaseRotatingHandler):
sfn = "%s.%d" % (self.baseFilename, i)
dfn = "%s.%d" % (self.baseFilename, i + 1)
if os.path.exists(sfn):
- #print "%s -> %s" % (sfn, dfn)
if os.path.exists(dfn):
os.remove(dfn)
os.rename(sfn, dfn)
@@ -128,7 +133,6 @@ class RotatingFileHandler(BaseRotatingHandler):
if os.path.exists(dfn):
os.remove(dfn)
os.rename(self.baseFilename, dfn)
- #print "%s -> %s" % (self.baseFilename, dfn)
self.mode = 'w'
self.stream = self._open()
@@ -156,7 +160,7 @@ 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=False):
+ 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 = when.upper()
self.backupCount = backupCount
@@ -278,7 +282,6 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
t = int(time.time())
if t >= self.rolloverAt:
return 1
- #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
return 0
def getFilesToDelete(self):
@@ -326,14 +329,8 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
os.remove(dfn)
os.rename(self.baseFilename, dfn)
if self.backupCount > 0:
- # find the oldest log file and delete it
- #s = glob.glob(self.baseFilename + ".20*")
- #if len(s) > self.backupCount:
- # s.sort()
- # os.remove(s[0])
for s in self.getFilesToDelete():
os.remove(s)
- #print "%s -> %s" % (self.baseFilename, dfn)
self.mode = 'w'
self.stream = self._open()
currentTime = int(time.time())
@@ -634,7 +631,8 @@ class SysLogHandler(logging.Handler):
LOG_NEWS = 7 # network news subsystem
LOG_UUCP = 8 # UUCP subsystem
LOG_CRON = 9 # clock daemon
- LOG_AUTHPRIV = 10 # security/authorization messages (private)
+ LOG_AUTHPRIV = 10 # security/authorization messages (private)
+ LOG_FTP = 11 # FTP daemon
# other codes through 15 reserved for system use
LOG_LOCAL0 = 16 # reserved for local use
@@ -666,6 +664,7 @@ class SysLogHandler(logging.Handler):
"authpriv": LOG_AUTHPRIV,
"cron": LOG_CRON,
"daemon": LOG_DAEMON,
+ "ftp": LOG_FTP,
"kern": LOG_KERN,
"lpr": LOG_LPR,
"mail": LOG_MAIL,
@@ -696,7 +695,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.
@@ -708,13 +708,16 @@ class SysLogHandler(logging.Handler):
self.address = address
self.facility = facility
+ self.socktype = socktype
+
if isinstance(address, str):
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):
@@ -727,12 +730,6 @@ class SysLogHandler(logging.Handler):
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.connect(address)
- # curious: when talking to the unix-domain '/dev/log' socket, a
- # zero-terminator seems to be required. this string is placed
- # into a class variable so that it can be overridden if
- # necessary.
- log_format_string = '<%d>%s\000'
-
def encodePriority(self, facility, priority):
"""
Encode the facility and priority. You can pass in strings or
@@ -771,18 +768,19 @@ class SysLogHandler(logging.Handler):
The record is formatted, and then sent to the syslog server. If
exception information is present, it is NOT sent to the server.
"""
- msg = self.format(record)
+ msg = self.format(record) + '\000'
"""
We need to convert record level to lowercase, maybe this will
change in the future.
"""
- msg = self.log_format_string % (
- self.encodePriority(self.facility,
- self.mapPriority(record.levelname)),
- msg)
+ prio = '<%d>' % self.encodePriority(self.facility,
+ self.mapPriority(record.levelname))
+ prio = prio.encode('utf-8')
+ # Message is a string. Convert to bytes as required by RFC 5424
msg = msg.encode('utf-8')
if codecs:
msg = codecs.BOM_UTF8 + msg
+ msg = prio + msg
try:
if self.unixsocket:
try:
@@ -790,8 +788,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:
@@ -801,7 +801,8 @@ 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):
+ def __init__(self, mailhost, fromaddr, toaddrs, subject,
+ credentials=None, secure=None):
"""
Initialize the handler.
@@ -809,7 +810,12 @@ class SMTPHandler(logging.Handler):
line of the email. To specify a non-standard SMTP port, use the
(host, port) tuple format for the mailhost argument. To specify
authentication credentials, supply a (username, password) tuple
- for the credentials argument.
+ for the credentials argument. To specify the use of a secure
+ protocol (TLS), pass in a tuple for the secure argument. This will
+ only be used when authentication credentials are supplied. The tuple
+ 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).
"""
logging.Handler.__init__(self)
if isinstance(mailhost, tuple):
@@ -825,6 +831,7 @@ class SMTPHandler(logging.Handler):
toaddrs = [toaddrs]
self.toaddrs = toaddrs
self.subject = subject
+ self.secure = secure
def getSubject(self, record):
"""
@@ -835,24 +842,6 @@ class SMTPHandler(logging.Handler):
"""
return self.subject
- weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-
- monthname = [None,
- 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-
- def date_time(self):
- """
- Return the current date and time formatted for a MIME header.
- Needed for Python 1.5.2 (no email package available)
- """
- year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
- s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
- self.weekdayname[wd],
- day, self.monthname[month], year,
- hh, mm, ss)
- return s
-
def emit(self, record):
"""
Emit a record.
@@ -861,10 +850,7 @@ class SMTPHandler(logging.Handler):
"""
try:
import smtplib
- try:
- from email.utils import formatdate
- except ImportError:
- formatdate = self.date_time
+ from email.utils import formatdate
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
@@ -876,6 +862,10 @@ class SMTPHandler(logging.Handler):
self.getSubject(record),
formatdate(), msg)
if self.username:
+ if self.secure is not None:
+ smtp.ehlo()
+ smtp.starttls(*self.secure)
+ smtp.ehlo()
smtp.login(self.username, self.password)
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
@@ -989,7 +979,7 @@ class HTTPHandler(logging.Handler):
A class which sends records to a Web server, using either GET or
POST semantics.
"""
- def __init__(self, host, url, method="GET"):
+ def __init__(self, host, url, method="GET", secure=False, credentials=None):
"""
Initialize the instance with the host, the request URL, and the method
("GET" or "POST")
@@ -1001,12 +991,14 @@ class HTTPHandler(logging.Handler):
self.host = host
self.url = url
self.method = method
+ self.secure = secure
+ self.credentials = credentials
def mapLogRecord(self, record):
"""
Default implementation of mapping the log record into a dict
that is sent as the CGI data. Overwrite in your class.
- Contributed by Franz Glasner.
+ Contributed by Franz Glasner.
"""
return record.__dict__
@@ -1019,7 +1011,10 @@ class HTTPHandler(logging.Handler):
try:
import http.client, urllib.parse
host = self.host
- h = http.client.HTTP(host)
+ if self.secure:
+ h = http.client.HTTPSConnection(host)
+ else:
+ h = http.client.HTTPConnection(host)
url = self.url
data = urllib.parse.urlencode(self.mapLogRecord(record))
if self.method == "GET":
@@ -1039,8 +1034,13 @@ class HTTPHandler(logging.Handler):
h.putheader("Content-type",
"application/x-www-form-urlencoded")
h.putheader("Content-length", str(len(data)))
+ if self.credentials:
+ import base64
+ 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.getreply() #can't do anything with the result
+ h.getresponse() #can't do anything with the result
except (KeyboardInterrupt, SystemExit):
raise
except:
@@ -1133,6 +1133,8 @@ class MemoryHandler(BufferingHandler):
For a MemoryHandler, flushing means just sending the buffered
records to the target, if there is one. Override if you want
different behaviour.
+
+ The record buffer is also cleared by this operation.
"""
if self.target:
for record in self.buffer:
@@ -1146,3 +1148,174 @@ class MemoryHandler(BufferingHandler):
self.flush()
self.target = None
BufferingHandler.close(self)
+
+
+class QueueHandler(logging.Handler):
+ """
+ This handler sends events to a queue. Typically, it would be used together
+ with a multiprocessing Queue to centralise logging to file in one process
+ (in a multi-process application), so as to avoid file write contention
+ between processes.
+
+ This code is new in Python 3.2, but this class can be copy pasted into
+ user code for use with earlier Python versions.
+ """
+
+ def __init__(self, queue):
+ """
+ Initialise an instance, using the passed queue.
+ """
+ logging.Handler.__init__(self)
+ self.queue = queue
+
+ def enqueue(self, record):
+ """
+ Enqueue a record.
+
+ The base implementation uses put_nowait. You may want to override
+ this method if you want to use blocking, timeouts or custom queue
+ implementations.
+ """
+ self.queue.put_nowait(record)
+
+ def prepare(self, record):
+ """
+ Prepares a record for queuing. The object returned by this method is
+ enqueued.
+
+ The base implementation formats the record to merge the message
+ and arguments, and removes unpickleable items from the record
+ in-place.
+
+ You might want to override this method if you want to convert
+ the record to a dict or JSON string, or send a modified copy
+ of the record while leaving the original intact.
+ """
+ # The format operation gets traceback text into record.exc_text
+ # (if there's exception data), and also puts the message into
+ # record.message. We can then use this to replace the original
+ # msg + args, as these might be unpickleable. We also zap the
+ # exc_info attribute, as it's no longer needed and, if not None,
+ # will typically not be pickleable.
+ self.format(record)
+ record.msg = record.message
+ record.args = None
+ record.exc_info = None
+ return record
+
+ def emit(self, record):
+ """
+ Emit a record.
+
+ Writes the LogRecord to the queue, preparing it for pickling first.
+ """
+ try:
+ self.enqueue(self.prepare(record))
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
+
+class QueueListener(object):
+ """
+ This class implements an internal threaded listener which watches for
+ LogRecords being added to a queue, removes them and passes them to a
+ list of handlers for processing.
+ """
+ _sentinel = None
+
+ def __init__(self, queue, *handlers):
+ """
+ Initialise an instance with the specified queue and
+ handlers.
+ """
+ self.queue = queue
+ self.handlers = handlers
+ self._stop = threading.Event()
+ self._thread = None
+
+ def dequeue(self, block):
+ """
+ Dequeue a record and return it, optionally blocking.
+
+ The base implementation uses get. You may want to override this method
+ if you want to use timeouts or work with custom queue implementations.
+ """
+ return self.queue.get(block)
+
+ def start(self):
+ """
+ Start the listener.
+
+ This starts up a background thread to monitor the queue for
+ LogRecords to process.
+ """
+ self._thread = t = threading.Thread(target=self._monitor)
+ t.setDaemon(True)
+ t.start()
+
+ def prepare(self , record):
+ """
+ Prepare a record for handling.
+
+ This method just returns the passed-in record. You may want to
+ override this method if you need to do any custom marshalling or
+ manipulation of the record before passing it to the handlers.
+ """
+ return record
+
+ def handle(self, record):
+ """
+ Handle a record.
+
+ This just loops through the handlers offering them the record
+ to handle.
+ """
+ record = self.prepare(record)
+ for handler in self.handlers:
+ handler.handle(record)
+
+ def _monitor(self):
+ """
+ Monitor the queue for records, and ask the handler
+ to deal with them.
+
+ This method runs on a separate, internal thread.
+ The thread will terminate if it sees a sentinel object in the queue.
+ """
+ q = self.queue
+ has_task_done = hasattr(q, 'task_done')
+ while not self._stop.isSet():
+ try:
+ record = self.dequeue(True)
+ if record is self._sentinel:
+ break
+ self.handle(record)
+ if has_task_done:
+ q.task_done()
+ except queue.Empty:
+ pass
+ # There might still be records in the queue.
+ while True:
+ try:
+ record = self.dequeue(False)
+ if record is self._sentinel:
+ break
+ self.handle(record)
+ if has_task_done:
+ q.task_done()
+ except queue.Empty:
+ break
+
+ def stop(self):
+ """
+ Stop the listener.
+
+ This asks the thread to terminate, and then waits for it to do so.
+ Note that if you don't call this before your application exits, there
+ may be some records still left on the queue, which won't be processed.
+ """
+ self._stop.set()
+ self.queue.put_nowait(self._sentinel)
+ self._thread.join()
+ self._thread = None