logging.config

Configuration functions for the logging package for Python. The core package is based on PEP 282 and comments thereto in comp.lang.python, and influenced by Apache's log4j system.

Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved.

To use, simply 'import logging' and log away!

   1# Copyright 2001-2023 by Vinay Sajip. All Rights Reserved.
   2#
   3# Permission to use, copy, modify, and distribute this software and its
   4# documentation for any purpose and without fee is hereby granted,
   5# provided that the above copyright notice appear in all copies and that
   6# both that copyright notice and this permission notice appear in
   7# supporting documentation, and that the name of Vinay Sajip
   8# not be used in advertising or publicity pertaining to distribution
   9# of the software without specific, written prior permission.
  10# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  11# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  12# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
  13# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  14# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16
  17"""
  18Configuration functions for the logging package for Python. The core package
  19is based on PEP 282 and comments thereto in comp.lang.python, and influenced
  20by Apache's log4j system.
  21
  22Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved.
  23
  24To use, simply 'import logging' and log away!
  25"""
  26
  27import errno
  28import functools
  29import io
  30import logging
  31import logging.handlers
  32import os
  33import queue
  34import re
  35import struct
  36import threading
  37import traceback
  38
  39from socketserver import ThreadingTCPServer, StreamRequestHandler
  40
  41
  42DEFAULT_LOGGING_CONFIG_PORT = 9030
  43
  44RESET_ERROR = errno.ECONNRESET
  45
  46#
  47#   The following code implements a socket listener for on-the-fly
  48#   reconfiguration of logging.
  49#
  50#   _listener holds the server object doing the listening
  51_listener = None
  52
  53def fileConfig(fname, defaults=None, disable_existing_loggers=True, encoding=None):
  54    """
  55    Read the logging configuration from a ConfigParser-format file.
  56
  57    This can be called several times from an application, allowing an end user
  58    the ability to select from various pre-canned configurations (if the
  59    developer provides a mechanism to present the choices and load the chosen
  60    configuration).
  61    """
  62    import configparser
  63
  64    if isinstance(fname, str):
  65        if not os.path.exists(fname):
  66            raise FileNotFoundError(f"{fname} doesn't exist")
  67        elif not os.path.getsize(fname):
  68            raise RuntimeError(f'{fname} is an empty file')
  69
  70    if isinstance(fname, configparser.RawConfigParser):
  71        cp = fname
  72    else:
  73        try:
  74            cp = configparser.ConfigParser(defaults)
  75            if hasattr(fname, 'readline'):
  76                cp.read_file(fname)
  77            else:
  78                encoding = io.text_encoding(encoding)
  79                cp.read(fname, encoding=encoding)
  80        except configparser.ParsingError as e:
  81            raise RuntimeError(f'{fname} is invalid: {e}')
  82
  83    formatters = _create_formatters(cp)
  84
  85    # critical section
  86    logging._acquireLock()
  87    try:
  88        _clearExistingHandlers()
  89
  90        # Handlers add themselves to logging._handlers
  91        handlers = _install_handlers(cp, formatters)
  92        _install_loggers(cp, handlers, disable_existing_loggers)
  93    finally:
  94        logging._releaseLock()
  95
  96
  97def _resolve(name):
  98    """Resolve a dotted name to a global object."""
  99    name = name.split('.')
 100    used = name.pop(0)
 101    found = __import__(used)
 102    for n in name:
 103        used = used + '.' + n
 104        try:
 105            found = getattr(found, n)
 106        except AttributeError:
 107            __import__(used)
 108            found = getattr(found, n)
 109    return found
 110
 111def _strip_spaces(alist):
 112    return map(str.strip, alist)
 113
 114def _create_formatters(cp):
 115    """Create and return formatters"""
 116    flist = cp["formatters"]["keys"]
 117    if not len(flist):
 118        return {}
 119    flist = flist.split(",")
 120    flist = _strip_spaces(flist)
 121    formatters = {}
 122    for form in flist:
 123        sectname = "formatter_%s" % form
 124        fs = cp.get(sectname, "format", raw=True, fallback=None)
 125        dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
 126        stl = cp.get(sectname, "style", raw=True, fallback='%')
 127        defaults = cp.get(sectname, "defaults", raw=True, fallback=None)
 128
 129        c = logging.Formatter
 130        class_name = cp[sectname].get("class")
 131        if class_name:
 132            c = _resolve(class_name)
 133
 134        if defaults is not None:
 135            defaults = eval(defaults, vars(logging))
 136            f = c(fs, dfs, stl, defaults=defaults)
 137        else:
 138            f = c(fs, dfs, stl)
 139        formatters[form] = f
 140    return formatters
 141
 142
 143def _install_handlers(cp, formatters):
 144    """Install and return handlers"""
 145    hlist = cp["handlers"]["keys"]
 146    if not len(hlist):
 147        return {}
 148    hlist = hlist.split(",")
 149    hlist = _strip_spaces(hlist)
 150    handlers = {}
 151    fixups = [] #for inter-handler references
 152    for hand in hlist:
 153        section = cp["handler_%s" % hand]
 154        klass = section["class"]
 155        fmt = section.get("formatter", "")
 156        try:
 157            klass = eval(klass, vars(logging))
 158        except (AttributeError, NameError):
 159            klass = _resolve(klass)
 160        args = section.get("args", '()')
 161        args = eval(args, vars(logging))
 162        kwargs = section.get("kwargs", '{}')
 163        kwargs = eval(kwargs, vars(logging))
 164        h = klass(*args, **kwargs)
 165        h.name = hand
 166        if "level" in section:
 167            level = section["level"]
 168            h.setLevel(level)
 169        if len(fmt):
 170            h.setFormatter(formatters[fmt])
 171        if issubclass(klass, logging.handlers.MemoryHandler):
 172            target = section.get("target", "")
 173            if len(target): #the target handler may not be loaded yet, so keep for later...
 174                fixups.append((h, target))
 175        handlers[hand] = h
 176    #now all handlers are loaded, fixup inter-handler references...
 177    for h, t in fixups:
 178        h.setTarget(handlers[t])
 179    return handlers
 180
 181def _handle_existing_loggers(existing, child_loggers, disable_existing):
 182    """
 183    When (re)configuring logging, handle loggers which were in the previous
 184    configuration but are not in the new configuration. There's no point
 185    deleting them as other threads may continue to hold references to them;
 186    and by disabling them, you stop them doing any logging.
 187
 188    However, don't disable children of named loggers, as that's probably not
 189    what was intended by the user. Also, allow existing loggers to NOT be
 190    disabled if disable_existing is false.
 191    """
 192    root = logging.root
 193    for log in existing:
 194        logger = root.manager.loggerDict[log]
 195        if log in child_loggers:
 196            if not isinstance(logger, logging.PlaceHolder):
 197                logger.setLevel(logging.NOTSET)
 198                logger.handlers = []
 199                logger.propagate = True
 200        else:
 201            logger.disabled = disable_existing
 202
 203def _install_loggers(cp, handlers, disable_existing):
 204    """Create and install loggers"""
 205
 206    # configure the root first
 207    llist = cp["loggers"]["keys"]
 208    llist = llist.split(",")
 209    llist = list(_strip_spaces(llist))
 210    llist.remove("root")
 211    section = cp["logger_root"]
 212    root = logging.root
 213    log = root
 214    if "level" in section:
 215        level = section["level"]
 216        log.setLevel(level)
 217    for h in root.handlers[:]:
 218        root.removeHandler(h)
 219    hlist = section["handlers"]
 220    if len(hlist):
 221        hlist = hlist.split(",")
 222        hlist = _strip_spaces(hlist)
 223        for hand in hlist:
 224            log.addHandler(handlers[hand])
 225
 226    #and now the others...
 227    #we don't want to lose the existing loggers,
 228    #since other threads may have pointers to them.
 229    #existing is set to contain all existing loggers,
 230    #and as we go through the new configuration we
 231    #remove any which are configured. At the end,
 232    #what's left in existing is the set of loggers
 233    #which were in the previous configuration but
 234    #which are not in the new configuration.
 235    existing = list(root.manager.loggerDict.keys())
 236    #The list needs to be sorted so that we can
 237    #avoid disabling child loggers of explicitly
 238    #named loggers. With a sorted list it is easier
 239    #to find the child loggers.
 240    existing.sort()
 241    #We'll keep the list of existing loggers
 242    #which are children of named loggers here...
 243    child_loggers = []
 244    #now set up the new ones...
 245    for log in llist:
 246        section = cp["logger_%s" % log]
 247        qn = section["qualname"]
 248        propagate = section.getint("propagate", fallback=1)
 249        logger = logging.getLogger(qn)
 250        if qn in existing:
 251            i = existing.index(qn) + 1 # start with the entry after qn
 252            prefixed = qn + "."
 253            pflen = len(prefixed)
 254            num_existing = len(existing)
 255            while i < num_existing:
 256                if existing[i][:pflen] == prefixed:
 257                    child_loggers.append(existing[i])
 258                i += 1
 259            existing.remove(qn)
 260        if "level" in section:
 261            level = section["level"]
 262            logger.setLevel(level)
 263        for h in logger.handlers[:]:
 264            logger.removeHandler(h)
 265        logger.propagate = propagate
 266        logger.disabled = 0
 267        hlist = section["handlers"]
 268        if len(hlist):
 269            hlist = hlist.split(",")
 270            hlist = _strip_spaces(hlist)
 271            for hand in hlist:
 272                logger.addHandler(handlers[hand])
 273
 274    #Disable any old loggers. There's no point deleting
 275    #them as other threads may continue to hold references
 276    #and by disabling them, you stop them doing any logging.
 277    #However, don't disable children of named loggers, as that's
 278    #probably not what was intended by the user.
 279    #for log in existing:
 280    #    logger = root.manager.loggerDict[log]
 281    #    if log in child_loggers:
 282    #        logger.level = logging.NOTSET
 283    #        logger.handlers = []
 284    #        logger.propagate = 1
 285    #    elif disable_existing_loggers:
 286    #        logger.disabled = 1
 287    _handle_existing_loggers(existing, child_loggers, disable_existing)
 288
 289
 290def _clearExistingHandlers():
 291    """Clear and close existing handlers"""
 292    logging._handlers.clear()
 293    logging.shutdown(logging._handlerList[:])
 294    del logging._handlerList[:]
 295
 296
 297IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
 298
 299
 300def valid_ident(s):
 301    m = IDENTIFIER.match(s)
 302    if not m:
 303        raise ValueError('Not a valid Python identifier: %r' % s)
 304    return True
 305
 306
 307class ConvertingMixin(object):
 308    """For ConvertingXXX's, this mixin class provides common functions"""
 309
 310    def convert_with_key(self, key, value, replace=True):
 311        result = self.configurator.convert(value)
 312        #If the converted value is different, save for next time
 313        if value is not result:
 314            if replace:
 315                self[key] = result
 316            if type(result) in (ConvertingDict, ConvertingList,
 317                               ConvertingTuple):
 318                result.parent = self
 319                result.key = key
 320        return result
 321
 322    def convert(self, value):
 323        result = self.configurator.convert(value)
 324        if value is not result:
 325            if type(result) in (ConvertingDict, ConvertingList,
 326                               ConvertingTuple):
 327                result.parent = self
 328        return result
 329
 330
 331# The ConvertingXXX classes are wrappers around standard Python containers,
 332# and they serve to convert any suitable values in the container. The
 333# conversion converts base dicts, lists and tuples to their wrapped
 334# equivalents, whereas strings which match a conversion format are converted
 335# appropriately.
 336#
 337# Each wrapper should have a configurator attribute holding the actual
 338# configurator to use for conversion.
 339
 340class ConvertingDict(dict, ConvertingMixin):
 341    """A converting dictionary wrapper."""
 342
 343    def __getitem__(self, key):
 344        value = dict.__getitem__(self, key)
 345        return self.convert_with_key(key, value)
 346
 347    def get(self, key, default=None):
 348        value = dict.get(self, key, default)
 349        return self.convert_with_key(key, value)
 350
 351    def pop(self, key, default=None):
 352        value = dict.pop(self, key, default)
 353        return self.convert_with_key(key, value, replace=False)
 354
 355class ConvertingList(list, ConvertingMixin):
 356    """A converting list wrapper."""
 357    def __getitem__(self, key):
 358        value = list.__getitem__(self, key)
 359        return self.convert_with_key(key, value)
 360
 361    def pop(self, idx=-1):
 362        value = list.pop(self, idx)
 363        return self.convert(value)
 364
 365class ConvertingTuple(tuple, ConvertingMixin):
 366    """A converting tuple wrapper."""
 367    def __getitem__(self, key):
 368        value = tuple.__getitem__(self, key)
 369        # Can't replace a tuple entry.
 370        return self.convert_with_key(key, value, replace=False)
 371
 372class BaseConfigurator(object):
 373    """
 374    The configurator base class which defines some useful defaults.
 375    """
 376
 377    CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
 378
 379    WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
 380    DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
 381    INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
 382    DIGIT_PATTERN = re.compile(r'^\d+$')
 383
 384    value_converters = {
 385        'ext' : 'ext_convert',
 386        'cfg' : 'cfg_convert',
 387    }
 388
 389    # We might want to use a different one, e.g. importlib
 390    importer = staticmethod(__import__)
 391
 392    def __init__(self, config):
 393        self.config = ConvertingDict(config)
 394        self.config.configurator = self
 395
 396    def resolve(self, s):
 397        """
 398        Resolve strings to objects using standard import and attribute
 399        syntax.
 400        """
 401        name = s.split('.')
 402        used = name.pop(0)
 403        try:
 404            found = self.importer(used)
 405            for frag in name:
 406                used += '.' + frag
 407                try:
 408                    found = getattr(found, frag)
 409                except AttributeError:
 410                    self.importer(used)
 411                    found = getattr(found, frag)
 412            return found
 413        except ImportError as e:
 414            v = ValueError('Cannot resolve %r: %s' % (s, e))
 415            raise v from e
 416
 417    def ext_convert(self, value):
 418        """Default converter for the ext:// protocol."""
 419        return self.resolve(value)
 420
 421    def cfg_convert(self, value):
 422        """Default converter for the cfg:// protocol."""
 423        rest = value
 424        m = self.WORD_PATTERN.match(rest)
 425        if m is None:
 426            raise ValueError("Unable to convert %r" % value)
 427        else:
 428            rest = rest[m.end():]
 429            d = self.config[m.groups()[0]]
 430            #print d, rest
 431            while rest:
 432                m = self.DOT_PATTERN.match(rest)
 433                if m:
 434                    d = d[m.groups()[0]]
 435                else:
 436                    m = self.INDEX_PATTERN.match(rest)
 437                    if m:
 438                        idx = m.groups()[0]
 439                        if not self.DIGIT_PATTERN.match(idx):
 440                            d = d[idx]
 441                        else:
 442                            try:
 443                                n = int(idx) # try as number first (most likely)
 444                                d = d[n]
 445                            except TypeError:
 446                                d = d[idx]
 447                if m:
 448                    rest = rest[m.end():]
 449                else:
 450                    raise ValueError('Unable to convert '
 451                                     '%r at %r' % (value, rest))
 452        #rest should be empty
 453        return d
 454
 455    def convert(self, value):
 456        """
 457        Convert values to an appropriate type. dicts, lists and tuples are
 458        replaced by their converting alternatives. Strings are checked to
 459        see if they have a conversion format and are converted if they do.
 460        """
 461        if not isinstance(value, ConvertingDict) and isinstance(value, dict):
 462            value = ConvertingDict(value)
 463            value.configurator = self
 464        elif not isinstance(value, ConvertingList) and isinstance(value, list):
 465            value = ConvertingList(value)
 466            value.configurator = self
 467        elif not isinstance(value, ConvertingTuple) and\
 468                 isinstance(value, tuple) and not hasattr(value, '_fields'):
 469            value = ConvertingTuple(value)
 470            value.configurator = self
 471        elif isinstance(value, str): # str for py3k
 472            m = self.CONVERT_PATTERN.match(value)
 473            if m:
 474                d = m.groupdict()
 475                prefix = d['prefix']
 476                converter = self.value_converters.get(prefix, None)
 477                if converter:
 478                    suffix = d['suffix']
 479                    converter = getattr(self, converter)
 480                    value = converter(suffix)
 481        return value
 482
 483    def configure_custom(self, config):
 484        """Configure an object with a user-supplied factory."""
 485        c = config.pop('()')
 486        if not callable(c):
 487            c = self.resolve(c)
 488        # Check for valid identifiers
 489        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
 490        result = c(**kwargs)
 491        props = config.pop('.', None)
 492        if props:
 493            for name, value in props.items():
 494                setattr(result, name, value)
 495        return result
 496
 497    def as_tuple(self, value):
 498        """Utility function which converts lists to tuples."""
 499        if isinstance(value, list):
 500            value = tuple(value)
 501        return value
 502
 503def _is_queue_like_object(obj):
 504    """Check that *obj* implements the Queue API."""
 505    if isinstance(obj, queue.Queue):
 506        return True
 507    # defer importing multiprocessing as much as possible
 508    from multiprocessing.queues import Queue as MPQueue
 509    if isinstance(obj, MPQueue):
 510        return True
 511    # Depending on the multiprocessing start context, we cannot create
 512    # a multiprocessing.managers.BaseManager instance 'mm' to get the
 513    # runtime type of mm.Queue() or mm.JoinableQueue() (see gh-119819).
 514    #
 515    # Since we only need an object implementing the Queue API, we only
 516    # do a protocol check, but we do not use typing.runtime_checkable()
 517    # and typing.Protocol to reduce import time (see gh-121723).
 518    #
 519    # Ideally, we would have wanted to simply use strict type checking
 520    # instead of a protocol-based type checking since the latter does
 521    # not check the method signatures.
 522    queue_interface = [
 523        'empty', 'full', 'get', 'get_nowait',
 524        'put', 'put_nowait', 'join', 'qsize',
 525        'task_done',
 526    ]
 527    return all(callable(getattr(obj, method, None))
 528               for method in queue_interface)
 529
 530class DictConfigurator(BaseConfigurator):
 531    """
 532    Configure logging using a dictionary-like object to describe the
 533    configuration.
 534    """
 535
 536    def configure(self):
 537        """Do the configuration."""
 538
 539        config = self.config
 540        if 'version' not in config:
 541            raise ValueError("dictionary doesn't specify a version")
 542        if config['version'] != 1:
 543            raise ValueError("Unsupported version: %s" % config['version'])
 544        incremental = config.pop('incremental', False)
 545        EMPTY_DICT = {}
 546        logging._acquireLock()
 547        try:
 548            if incremental:
 549                handlers = config.get('handlers', EMPTY_DICT)
 550                for name in handlers:
 551                    if name not in logging._handlers:
 552                        raise ValueError('No handler found with '
 553                                         'name %r'  % name)
 554                    else:
 555                        try:
 556                            handler = logging._handlers[name]
 557                            handler_config = handlers[name]
 558                            level = handler_config.get('level', None)
 559                            if level:
 560                                handler.setLevel(logging._checkLevel(level))
 561                        except Exception as e:
 562                            raise ValueError('Unable to configure handler '
 563                                             '%r' % name) from e
 564                loggers = config.get('loggers', EMPTY_DICT)
 565                for name in loggers:
 566                    try:
 567                        self.configure_logger(name, loggers[name], True)
 568                    except Exception as e:
 569                        raise ValueError('Unable to configure logger '
 570                                         '%r' % name) from e
 571                root = config.get('root', None)
 572                if root:
 573                    try:
 574                        self.configure_root(root, True)
 575                    except Exception as e:
 576                        raise ValueError('Unable to configure root '
 577                                         'logger') from e
 578            else:
 579                disable_existing = config.pop('disable_existing_loggers', True)
 580
 581                _clearExistingHandlers()
 582
 583                # Do formatters first - they don't refer to anything else
 584                formatters = config.get('formatters', EMPTY_DICT)
 585                for name in formatters:
 586                    try:
 587                        formatters[name] = self.configure_formatter(
 588                                                            formatters[name])
 589                    except Exception as e:
 590                        raise ValueError('Unable to configure '
 591                                         'formatter %r' % name) from e
 592                # Next, do filters - they don't refer to anything else, either
 593                filters = config.get('filters', EMPTY_DICT)
 594                for name in filters:
 595                    try:
 596                        filters[name] = self.configure_filter(filters[name])
 597                    except Exception as e:
 598                        raise ValueError('Unable to configure '
 599                                         'filter %r' % name) from e
 600
 601                # Next, do handlers - they refer to formatters and filters
 602                # As handlers can refer to other handlers, sort the keys
 603                # to allow a deterministic order of configuration
 604                handlers = config.get('handlers', EMPTY_DICT)
 605                deferred = []
 606                for name in sorted(handlers):
 607                    try:
 608                        handler = self.configure_handler(handlers[name])
 609                        handler.name = name
 610                        handlers[name] = handler
 611                    except Exception as e:
 612                        if ' not configured yet' in str(e.__cause__):
 613                            deferred.append(name)
 614                        else:
 615                            raise ValueError('Unable to configure handler '
 616                                             '%r' % name) from e
 617
 618                # Now do any that were deferred
 619                for name in deferred:
 620                    try:
 621                        handler = self.configure_handler(handlers[name])
 622                        handler.name = name
 623                        handlers[name] = handler
 624                    except Exception as e:
 625                        raise ValueError('Unable to configure handler '
 626                                         '%r' % name) from e
 627
 628                # Next, do loggers - they refer to handlers and filters
 629
 630                #we don't want to lose the existing loggers,
 631                #since other threads may have pointers to them.
 632                #existing is set to contain all existing loggers,
 633                #and as we go through the new configuration we
 634                #remove any which are configured. At the end,
 635                #what's left in existing is the set of loggers
 636                #which were in the previous configuration but
 637                #which are not in the new configuration.
 638                root = logging.root
 639                existing = list(root.manager.loggerDict.keys())
 640                #The list needs to be sorted so that we can
 641                #avoid disabling child loggers of explicitly
 642                #named loggers. With a sorted list it is easier
 643                #to find the child loggers.
 644                existing.sort()
 645                #We'll keep the list of existing loggers
 646                #which are children of named loggers here...
 647                child_loggers = []
 648                #now set up the new ones...
 649                loggers = config.get('loggers', EMPTY_DICT)
 650                for name in loggers:
 651                    if name in existing:
 652                        i = existing.index(name) + 1 # look after name
 653                        prefixed = name + "."
 654                        pflen = len(prefixed)
 655                        num_existing = len(existing)
 656                        while i < num_existing:
 657                            if existing[i][:pflen] == prefixed:
 658                                child_loggers.append(existing[i])
 659                            i += 1
 660                        existing.remove(name)
 661                    try:
 662                        self.configure_logger(name, loggers[name])
 663                    except Exception as e:
 664                        raise ValueError('Unable to configure logger '
 665                                         '%r' % name) from e
 666
 667                #Disable any old loggers. There's no point deleting
 668                #them as other threads may continue to hold references
 669                #and by disabling them, you stop them doing any logging.
 670                #However, don't disable children of named loggers, as that's
 671                #probably not what was intended by the user.
 672                #for log in existing:
 673                #    logger = root.manager.loggerDict[log]
 674                #    if log in child_loggers:
 675                #        logger.level = logging.NOTSET
 676                #        logger.handlers = []
 677                #        logger.propagate = True
 678                #    elif disable_existing:
 679                #        logger.disabled = True
 680                _handle_existing_loggers(existing, child_loggers,
 681                                         disable_existing)
 682
 683                # And finally, do the root logger
 684                root = config.get('root', None)
 685                if root:
 686                    try:
 687                        self.configure_root(root)
 688                    except Exception as e:
 689                        raise ValueError('Unable to configure root '
 690                                         'logger') from e
 691        finally:
 692            logging._releaseLock()
 693
 694    def configure_formatter(self, config):
 695        """Configure a formatter from a dictionary."""
 696        if '()' in config:
 697            factory = config['()'] # for use in exception handler
 698            try:
 699                result = self.configure_custom(config)
 700            except TypeError as te:
 701                if "'format'" not in str(te):
 702                    raise
 703                #Name of parameter changed from fmt to format.
 704                #Retry with old name.
 705                #This is so that code can be used with older Python versions
 706                #(e.g. by Django)
 707                config['fmt'] = config.pop('format')
 708                config['()'] = factory
 709                result = self.configure_custom(config)
 710        else:
 711            fmt = config.get('format', None)
 712            dfmt = config.get('datefmt', None)
 713            style = config.get('style', '%')
 714            cname = config.get('class', None)
 715            defaults = config.get('defaults', None)
 716
 717            if not cname:
 718                c = logging.Formatter
 719            else:
 720                c = _resolve(cname)
 721
 722            kwargs  = {}
 723
 724            # Add defaults only if it exists.
 725            # Prevents TypeError in custom formatter callables that do not
 726            # accept it.
 727            if defaults is not None:
 728                kwargs['defaults'] = defaults
 729
 730            # A TypeError would be raised if "validate" key is passed in with a formatter callable
 731            # that does not accept "validate" as a parameter
 732            if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
 733                result = c(fmt, dfmt, style, config['validate'], **kwargs)
 734            else:
 735                result = c(fmt, dfmt, style, **kwargs)
 736
 737        return result
 738
 739    def configure_filter(self, config):
 740        """Configure a filter from a dictionary."""
 741        if '()' in config:
 742            result = self.configure_custom(config)
 743        else:
 744            name = config.get('name', '')
 745            result = logging.Filter(name)
 746        return result
 747
 748    def add_filters(self, filterer, filters):
 749        """Add filters to a filterer from a list of names."""
 750        for f in filters:
 751            try:
 752                if callable(f) or callable(getattr(f, 'filter', None)):
 753                    filter_ = f
 754                else:
 755                    filter_ = self.config['filters'][f]
 756                filterer.addFilter(filter_)
 757            except Exception as e:
 758                raise ValueError('Unable to add filter %r' % f) from e
 759
 760    def _configure_queue_handler(self, klass, **kwargs):
 761        if 'queue' in kwargs:
 762            q = kwargs.pop('queue')
 763        else:
 764            q = queue.Queue()  # unbounded
 765
 766        rhl = kwargs.pop('respect_handler_level', False)
 767        lklass = kwargs.pop('listener', logging.handlers.QueueListener)
 768        handlers = kwargs.pop('handlers', [])
 769
 770        listener = lklass(q, *handlers, respect_handler_level=rhl)
 771        handler = klass(q, **kwargs)
 772        handler.listener = listener
 773        return handler
 774
 775    def configure_handler(self, config):
 776        """Configure a handler from a dictionary."""
 777        config_copy = dict(config)  # for restoring in case of error
 778        formatter = config.pop('formatter', None)
 779        if formatter:
 780            try:
 781                formatter = self.config['formatters'][formatter]
 782            except Exception as e:
 783                raise ValueError('Unable to set formatter '
 784                                 '%r' % formatter) from e
 785        level = config.pop('level', None)
 786        filters = config.pop('filters', None)
 787        if '()' in config:
 788            c = config.pop('()')
 789            if not callable(c):
 790                c = self.resolve(c)
 791            factory = c
 792        else:
 793            cname = config.pop('class')
 794            if callable(cname):
 795                klass = cname
 796            else:
 797                klass = self.resolve(cname)
 798            if issubclass(klass, logging.handlers.MemoryHandler):
 799                if 'flushLevel' in config:
 800                    config['flushLevel'] = logging._checkLevel(config['flushLevel'])
 801                if 'target' in config:
 802                    # Special case for handler which refers to another handler
 803                    try:
 804                        tn = config['target']
 805                        th = self.config['handlers'][tn]
 806                        if not isinstance(th, logging.Handler):
 807                            config.update(config_copy)  # restore for deferred cfg
 808                            raise TypeError('target not configured yet')
 809                        config['target'] = th
 810                    except Exception as e:
 811                        raise ValueError('Unable to set target handler %r' % tn) from e
 812            elif issubclass(klass, logging.handlers.QueueHandler):
 813                # Another special case for handler which refers to other handlers
 814                # if 'handlers' not in config:
 815                    # raise ValueError('No handlers specified for a QueueHandler')
 816                if 'queue' in config:
 817                    qspec = config['queue']
 818
 819                    if isinstance(qspec, str):
 820                        q = self.resolve(qspec)
 821                        if not callable(q):
 822                            raise TypeError('Invalid queue specifier %r' % qspec)
 823                        config['queue'] = q()
 824                    elif isinstance(qspec, dict):
 825                        if '()' not in qspec:
 826                            raise TypeError('Invalid queue specifier %r' % qspec)
 827                        config['queue'] = self.configure_custom(dict(qspec))
 828                    elif not _is_queue_like_object(qspec):
 829                        raise TypeError('Invalid queue specifier %r' % qspec)
 830
 831                if 'listener' in config:
 832                    lspec = config['listener']
 833                    if isinstance(lspec, type):
 834                        if not issubclass(lspec, logging.handlers.QueueListener):
 835                            raise TypeError('Invalid listener specifier %r' % lspec)
 836                    else:
 837                        if isinstance(lspec, str):
 838                            listener = self.resolve(lspec)
 839                            if isinstance(listener, type) and\
 840                                not issubclass(listener, logging.handlers.QueueListener):
 841                                raise TypeError('Invalid listener specifier %r' % lspec)
 842                        elif isinstance(lspec, dict):
 843                            if '()' not in lspec:
 844                                raise TypeError('Invalid listener specifier %r' % lspec)
 845                            listener = self.configure_custom(dict(lspec))
 846                        else:
 847                            raise TypeError('Invalid listener specifier %r' % lspec)
 848                        if not callable(listener):
 849                            raise TypeError('Invalid listener specifier %r' % lspec)
 850                        config['listener'] = listener
 851                if 'handlers' in config:
 852                    hlist = []
 853                    try:
 854                        for hn in config['handlers']:
 855                            h = self.config['handlers'][hn]
 856                            if not isinstance(h, logging.Handler):
 857                                config.update(config_copy)  # restore for deferred cfg
 858                                raise TypeError('Required handler %r '
 859                                                'is not configured yet' % hn)
 860                            hlist.append(h)
 861                    except Exception as e:
 862                        raise ValueError('Unable to set required handler %r' % hn) from e
 863                    config['handlers'] = hlist
 864            elif issubclass(klass, logging.handlers.SMTPHandler) and\
 865                'mailhost' in config:
 866                config['mailhost'] = self.as_tuple(config['mailhost'])
 867            elif issubclass(klass, logging.handlers.SysLogHandler) and\
 868                'address' in config:
 869                config['address'] = self.as_tuple(config['address'])
 870            if issubclass(klass, logging.handlers.QueueHandler):
 871                factory = functools.partial(self._configure_queue_handler, klass)
 872            else:
 873                factory = klass
 874        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
 875        try:
 876            result = factory(**kwargs)
 877        except TypeError as te:
 878            if "'stream'" not in str(te):
 879                raise
 880            #The argument name changed from strm to stream
 881            #Retry with old name.
 882            #This is so that code can be used with older Python versions
 883            #(e.g. by Django)
 884            kwargs['strm'] = kwargs.pop('stream')
 885            result = factory(**kwargs)
 886        if formatter:
 887            result.setFormatter(formatter)
 888        if level is not None:
 889            result.setLevel(logging._checkLevel(level))
 890        if filters:
 891            self.add_filters(result, filters)
 892        props = config.pop('.', None)
 893        if props:
 894            for name, value in props.items():
 895                setattr(result, name, value)
 896        return result
 897
 898    def add_handlers(self, logger, handlers):
 899        """Add handlers to a logger from a list of names."""
 900        for h in handlers:
 901            try:
 902                logger.addHandler(self.config['handlers'][h])
 903            except Exception as e:
 904                raise ValueError('Unable to add handler %r' % h) from e
 905
 906    def common_logger_config(self, logger, config, incremental=False):
 907        """
 908        Perform configuration which is common to root and non-root loggers.
 909        """
 910        level = config.get('level', None)
 911        if level is not None:
 912            logger.setLevel(logging._checkLevel(level))
 913        if not incremental:
 914            #Remove any existing handlers
 915            for h in logger.handlers[:]:
 916                logger.removeHandler(h)
 917            handlers = config.get('handlers', None)
 918            if handlers:
 919                self.add_handlers(logger, handlers)
 920            filters = config.get('filters', None)
 921            if filters:
 922                self.add_filters(logger, filters)
 923
 924    def configure_logger(self, name, config, incremental=False):
 925        """Configure a non-root logger from a dictionary."""
 926        logger = logging.getLogger(name)
 927        self.common_logger_config(logger, config, incremental)
 928        logger.disabled = False
 929        propagate = config.get('propagate', None)
 930        if propagate is not None:
 931            logger.propagate = propagate
 932
 933    def configure_root(self, config, incremental=False):
 934        """Configure a root logger from a dictionary."""
 935        root = logging.getLogger()
 936        self.common_logger_config(root, config, incremental)
 937
 938dictConfigClass = DictConfigurator
 939
 940def dictConfig(config):
 941    """Configure logging using a dictionary."""
 942    dictConfigClass(config).configure()
 943
 944
 945def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
 946    """
 947    Start up a socket server on the specified port, and listen for new
 948    configurations.
 949
 950    These will be sent as a file suitable for processing by fileConfig().
 951    Returns a Thread object on which you can call start() to start the server,
 952    and which you can join() when appropriate. To stop the server, call
 953    stopListening().
 954
 955    Use the ``verify`` argument to verify any bytes received across the wire
 956    from a client. If specified, it should be a callable which receives a
 957    single argument - the bytes of configuration data received across the
 958    network - and it should return either ``None``, to indicate that the
 959    passed in bytes could not be verified and should be discarded, or a
 960    byte string which is then passed to the configuration machinery as
 961    normal. Note that you can return transformed bytes, e.g. by decrypting
 962    the bytes passed in.
 963    """
 964
 965    class ConfigStreamHandler(StreamRequestHandler):
 966        """
 967        Handler for a logging configuration request.
 968
 969        It expects a completely new logging configuration and uses fileConfig
 970        to install it.
 971        """
 972        def handle(self):
 973            """
 974            Handle a request.
 975
 976            Each request is expected to be a 4-byte length, packed using
 977            struct.pack(">L", n), followed by the config file.
 978            Uses fileConfig() to do the grunt work.
 979            """
 980            try:
 981                conn = self.connection
 982                chunk = conn.recv(4)
 983                if len(chunk) == 4:
 984                    slen = struct.unpack(">L", chunk)[0]
 985                    chunk = self.connection.recv(slen)
 986                    while len(chunk) < slen:
 987                        chunk = chunk + conn.recv(slen - len(chunk))
 988                    if self.server.verify is not None:
 989                        chunk = self.server.verify(chunk)
 990                    if chunk is not None:   # verified, can process
 991                        chunk = chunk.decode("utf-8")
 992                        try:
 993                            import json
 994                            d =json.loads(chunk)
 995                            assert isinstance(d, dict)
 996                            dictConfig(d)
 997                        except Exception:
 998                            #Apply new configuration.
 999
1000                            file = io.StringIO(chunk)
1001                            try:
1002                                fileConfig(file)
1003                            except Exception:
1004                                traceback.print_exc()
1005                    if self.server.ready:
1006                        self.server.ready.set()
1007            except OSError as e:
1008                if e.errno != RESET_ERROR:
1009                    raise
1010
1011    class ConfigSocketReceiver(ThreadingTCPServer):
1012        """
1013        A simple TCP socket-based logging config receiver.
1014        """
1015
1016        allow_reuse_address = 1
1017
1018        def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
1019                     handler=None, ready=None, verify=None):
1020            ThreadingTCPServer.__init__(self, (host, port), handler)
1021            logging._acquireLock()
1022            self.abort = 0
1023            logging._releaseLock()
1024            self.timeout = 1
1025            self.ready = ready
1026            self.verify = verify
1027
1028        def serve_until_stopped(self):
1029            import select
1030            abort = 0
1031            while not abort:
1032                rd, wr, ex = select.select([self.socket.fileno()],
1033                                           [], [],
1034                                           self.timeout)
1035                if rd:
1036                    self.handle_request()
1037                logging._acquireLock()
1038                abort = self.abort
1039                logging._releaseLock()
1040            self.server_close()
1041
1042    class Server(threading.Thread):
1043
1044        def __init__(self, rcvr, hdlr, port, verify):
1045            super(Server, self).__init__()
1046            self.rcvr = rcvr
1047            self.hdlr = hdlr
1048            self.port = port
1049            self.verify = verify
1050            self.ready = threading.Event()
1051
1052        def run(self):
1053            server = self.rcvr(port=self.port, handler=self.hdlr,
1054                               ready=self.ready,
1055                               verify=self.verify)
1056            if self.port == 0:
1057                self.port = server.server_address[1]
1058            self.ready.set()
1059            global _listener
1060            logging._acquireLock()
1061            _listener = server
1062            logging._releaseLock()
1063            server.serve_until_stopped()
1064
1065    return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify)
1066
1067def stopListening():
1068    """
1069    Stop the listening server which was created with a call to listen().
1070    """
1071    global _listener
1072    logging._acquireLock()
1073    try:
1074        if _listener:
1075            _listener.abort = 1
1076            _listener = None
1077    finally:
1078        logging._releaseLock()
DEFAULT_LOGGING_CONFIG_PORT = 9030
RESET_ERROR = 104
def fileConfig(fname, defaults=None, disable_existing_loggers=True, encoding=None):
54def fileConfig(fname, defaults=None, disable_existing_loggers=True, encoding=None):
55    """
56    Read the logging configuration from a ConfigParser-format file.
57
58    This can be called several times from an application, allowing an end user
59    the ability to select from various pre-canned configurations (if the
60    developer provides a mechanism to present the choices and load the chosen
61    configuration).
62    """
63    import configparser
64
65    if isinstance(fname, str):
66        if not os.path.exists(fname):
67            raise FileNotFoundError(f"{fname} doesn't exist")
68        elif not os.path.getsize(fname):
69            raise RuntimeError(f'{fname} is an empty file')
70
71    if isinstance(fname, configparser.RawConfigParser):
72        cp = fname
73    else:
74        try:
75            cp = configparser.ConfigParser(defaults)
76            if hasattr(fname, 'readline'):
77                cp.read_file(fname)
78            else:
79                encoding = io.text_encoding(encoding)
80                cp.read(fname, encoding=encoding)
81        except configparser.ParsingError as e:
82            raise RuntimeError(f'{fname} is invalid: {e}')
83
84    formatters = _create_formatters(cp)
85
86    # critical section
87    logging._acquireLock()
88    try:
89        _clearExistingHandlers()
90
91        # Handlers add themselves to logging._handlers
92        handlers = _install_handlers(cp, formatters)
93        _install_loggers(cp, handlers, disable_existing_loggers)
94    finally:
95        logging._releaseLock()

Read the logging configuration from a ConfigParser-format file.

This can be called several times from an application, allowing an end user the ability to select from various pre-canned configurations (if the developer provides a mechanism to present the choices and load the chosen configuration).

IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.IGNORECASE)
def valid_ident(s):
301def valid_ident(s):
302    m = IDENTIFIER.match(s)
303    if not m:
304        raise ValueError('Not a valid Python identifier: %r' % s)
305    return True
class ConvertingMixin:
308class ConvertingMixin(object):
309    """For ConvertingXXX's, this mixin class provides common functions"""
310
311    def convert_with_key(self, key, value, replace=True):
312        result = self.configurator.convert(value)
313        #If the converted value is different, save for next time
314        if value is not result:
315            if replace:
316                self[key] = result
317            if type(result) in (ConvertingDict, ConvertingList,
318                               ConvertingTuple):
319                result.parent = self
320                result.key = key
321        return result
322
323    def convert(self, value):
324        result = self.configurator.convert(value)
325        if value is not result:
326            if type(result) in (ConvertingDict, ConvertingList,
327                               ConvertingTuple):
328                result.parent = self
329        return result

For ConvertingXXX's, this mixin class provides common functions

def convert_with_key(self, key, value, replace=True):
311    def convert_with_key(self, key, value, replace=True):
312        result = self.configurator.convert(value)
313        #If the converted value is different, save for next time
314        if value is not result:
315            if replace:
316                self[key] = result
317            if type(result) in (ConvertingDict, ConvertingList,
318                               ConvertingTuple):
319                result.parent = self
320                result.key = key
321        return result
def convert(self, value):
323    def convert(self, value):
324        result = self.configurator.convert(value)
325        if value is not result:
326            if type(result) in (ConvertingDict, ConvertingList,
327                               ConvertingTuple):
328                result.parent = self
329        return result
class ConvertingDict(builtins.dict, ConvertingMixin):
341class ConvertingDict(dict, ConvertingMixin):
342    """A converting dictionary wrapper."""
343
344    def __getitem__(self, key):
345        value = dict.__getitem__(self, key)
346        return self.convert_with_key(key, value)
347
348    def get(self, key, default=None):
349        value = dict.get(self, key, default)
350        return self.convert_with_key(key, value)
351
352    def pop(self, key, default=None):
353        value = dict.pop(self, key, default)
354        return self.convert_with_key(key, value, replace=False)

A converting dictionary wrapper.

def get(self, key, default=None):
348    def get(self, key, default=None):
349        value = dict.get(self, key, default)
350        return self.convert_with_key(key, value)

Return the value for key if key is in the dictionary, else default.

def pop(self, key, default=None):
352    def pop(self, key, default=None):
353        value = dict.pop(self, key, default)
354        return self.convert_with_key(key, value, replace=False)

D.pop(k[,d]) -> v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

Inherited Members
ConvertingMixin
convert_with_key
convert
builtins.dict
setdefault
popitem
keys
items
values
update
fromkeys
clear
copy
class ConvertingList(builtins.list, ConvertingMixin):
356class ConvertingList(list, ConvertingMixin):
357    """A converting list wrapper."""
358    def __getitem__(self, key):
359        value = list.__getitem__(self, key)
360        return self.convert_with_key(key, value)
361
362    def pop(self, idx=-1):
363        value = list.pop(self, idx)
364        return self.convert(value)

A converting list wrapper.

def pop(self, idx=-1):
362    def pop(self, idx=-1):
363        value = list.pop(self, idx)
364        return self.convert(value)

Remove and return item at index (default last).

Raises IndexError if list is empty or index is out of range.

Inherited Members
builtins.list
list
clear
copy
append
insert
extend
remove
index
count
reverse
sort
ConvertingMixin
convert_with_key
convert
class ConvertingTuple(builtins.tuple, ConvertingMixin):
366class ConvertingTuple(tuple, ConvertingMixin):
367    """A converting tuple wrapper."""
368    def __getitem__(self, key):
369        value = tuple.__getitem__(self, key)
370        # Can't replace a tuple entry.
371        return self.convert_with_key(key, value, replace=False)

A converting tuple wrapper.

Inherited Members
ConvertingMixin
convert_with_key
convert
builtins.tuple
index
count
class BaseConfigurator:
373class BaseConfigurator(object):
374    """
375    The configurator base class which defines some useful defaults.
376    """
377
378    CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
379
380    WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
381    DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
382    INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
383    DIGIT_PATTERN = re.compile(r'^\d+$')
384
385    value_converters = {
386        'ext' : 'ext_convert',
387        'cfg' : 'cfg_convert',
388    }
389
390    # We might want to use a different one, e.g. importlib
391    importer = staticmethod(__import__)
392
393    def __init__(self, config):
394        self.config = ConvertingDict(config)
395        self.config.configurator = self
396
397    def resolve(self, s):
398        """
399        Resolve strings to objects using standard import and attribute
400        syntax.
401        """
402        name = s.split('.')
403        used = name.pop(0)
404        try:
405            found = self.importer(used)
406            for frag in name:
407                used += '.' + frag
408                try:
409                    found = getattr(found, frag)
410                except AttributeError:
411                    self.importer(used)
412                    found = getattr(found, frag)
413            return found
414        except ImportError as e:
415            v = ValueError('Cannot resolve %r: %s' % (s, e))
416            raise v from e
417
418    def ext_convert(self, value):
419        """Default converter for the ext:// protocol."""
420        return self.resolve(value)
421
422    def cfg_convert(self, value):
423        """Default converter for the cfg:// protocol."""
424        rest = value
425        m = self.WORD_PATTERN.match(rest)
426        if m is None:
427            raise ValueError("Unable to convert %r" % value)
428        else:
429            rest = rest[m.end():]
430            d = self.config[m.groups()[0]]
431            #print d, rest
432            while rest:
433                m = self.DOT_PATTERN.match(rest)
434                if m:
435                    d = d[m.groups()[0]]
436                else:
437                    m = self.INDEX_PATTERN.match(rest)
438                    if m:
439                        idx = m.groups()[0]
440                        if not self.DIGIT_PATTERN.match(idx):
441                            d = d[idx]
442                        else:
443                            try:
444                                n = int(idx) # try as number first (most likely)
445                                d = d[n]
446                            except TypeError:
447                                d = d[idx]
448                if m:
449                    rest = rest[m.end():]
450                else:
451                    raise ValueError('Unable to convert '
452                                     '%r at %r' % (value, rest))
453        #rest should be empty
454        return d
455
456    def convert(self, value):
457        """
458        Convert values to an appropriate type. dicts, lists and tuples are
459        replaced by their converting alternatives. Strings are checked to
460        see if they have a conversion format and are converted if they do.
461        """
462        if not isinstance(value, ConvertingDict) and isinstance(value, dict):
463            value = ConvertingDict(value)
464            value.configurator = self
465        elif not isinstance(value, ConvertingList) and isinstance(value, list):
466            value = ConvertingList(value)
467            value.configurator = self
468        elif not isinstance(value, ConvertingTuple) and\
469                 isinstance(value, tuple) and not hasattr(value, '_fields'):
470            value = ConvertingTuple(value)
471            value.configurator = self
472        elif isinstance(value, str): # str for py3k
473            m = self.CONVERT_PATTERN.match(value)
474            if m:
475                d = m.groupdict()
476                prefix = d['prefix']
477                converter = self.value_converters.get(prefix, None)
478                if converter:
479                    suffix = d['suffix']
480                    converter = getattr(self, converter)
481                    value = converter(suffix)
482        return value
483
484    def configure_custom(self, config):
485        """Configure an object with a user-supplied factory."""
486        c = config.pop('()')
487        if not callable(c):
488            c = self.resolve(c)
489        # Check for valid identifiers
490        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
491        result = c(**kwargs)
492        props = config.pop('.', None)
493        if props:
494            for name, value in props.items():
495                setattr(result, name, value)
496        return result
497
498    def as_tuple(self, value):
499        """Utility function which converts lists to tuples."""
500        if isinstance(value, list):
501            value = tuple(value)
502        return value

The configurator base class which defines some useful defaults.

BaseConfigurator(config)
393    def __init__(self, config):
394        self.config = ConvertingDict(config)
395        self.config.configurator = self
CONVERT_PATTERN = re.compile('^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
WORD_PATTERN = re.compile('^\\s*(\\w+)\\s*')
DOT_PATTERN = re.compile('^\\.\\s*(\\w+)\\s*')
INDEX_PATTERN = re.compile('^\\[\\s*(\\w+)\\s*\\]\\s*')
DIGIT_PATTERN = re.compile('^\\d+$')
value_converters = {'ext': 'ext_convert', 'cfg': 'cfg_convert'}
def importer(name, globals=None, locals=None, fromlist=(), level=0):

Import a module.

Because this function is meant for use by the Python interpreter and not for general use, it is better to use importlib.import_module() to programmatically import a module.

The globals argument is only used to determine the context; they are not modified. The locals argument is unused. The fromlist should be a list of names to emulate from name import ..., or an empty list to emulate import name. When importing a module from a package, note that __import__('A.B', ...) returns package A when fromlist is empty, but its submodule B when fromlist is not empty. The level argument is used to determine whether to perform absolute or relative imports: 0 is absolute, while a positive number is the number of parent directories to search relative to the current module.

config
def resolve(self, s):
397    def resolve(self, s):
398        """
399        Resolve strings to objects using standard import and attribute
400        syntax.
401        """
402        name = s.split('.')
403        used = name.pop(0)
404        try:
405            found = self.importer(used)
406            for frag in name:
407                used += '.' + frag
408                try:
409                    found = getattr(found, frag)
410                except AttributeError:
411                    self.importer(used)
412                    found = getattr(found, frag)
413            return found
414        except ImportError as e:
415            v = ValueError('Cannot resolve %r: %s' % (s, e))
416            raise v from e

Resolve strings to objects using standard import and attribute syntax.

def ext_convert(self, value):
418    def ext_convert(self, value):
419        """Default converter for the ext:// protocol."""
420        return self.resolve(value)

Default converter for the ext:// protocol.

def cfg_convert(self, value):
422    def cfg_convert(self, value):
423        """Default converter for the cfg:// protocol."""
424        rest = value
425        m = self.WORD_PATTERN.match(rest)
426        if m is None:
427            raise ValueError("Unable to convert %r" % value)
428        else:
429            rest = rest[m.end():]
430            d = self.config[m.groups()[0]]
431            #print d, rest
432            while rest:
433                m = self.DOT_PATTERN.match(rest)
434                if m:
435                    d = d[m.groups()[0]]
436                else:
437                    m = self.INDEX_PATTERN.match(rest)
438                    if m:
439                        idx = m.groups()[0]
440                        if not self.DIGIT_PATTERN.match(idx):
441                            d = d[idx]
442                        else:
443                            try:
444                                n = int(idx) # try as number first (most likely)
445                                d = d[n]
446                            except TypeError:
447                                d = d[idx]
448                if m:
449                    rest = rest[m.end():]
450                else:
451                    raise ValueError('Unable to convert '
452                                     '%r at %r' % (value, rest))
453        #rest should be empty
454        return d

Default converter for the cfg:// protocol.

def convert(self, value):
456    def convert(self, value):
457        """
458        Convert values to an appropriate type. dicts, lists and tuples are
459        replaced by their converting alternatives. Strings are checked to
460        see if they have a conversion format and are converted if they do.
461        """
462        if not isinstance(value, ConvertingDict) and isinstance(value, dict):
463            value = ConvertingDict(value)
464            value.configurator = self
465        elif not isinstance(value, ConvertingList) and isinstance(value, list):
466            value = ConvertingList(value)
467            value.configurator = self
468        elif not isinstance(value, ConvertingTuple) and\
469                 isinstance(value, tuple) and not hasattr(value, '_fields'):
470            value = ConvertingTuple(value)
471            value.configurator = self
472        elif isinstance(value, str): # str for py3k
473            m = self.CONVERT_PATTERN.match(value)
474            if m:
475                d = m.groupdict()
476                prefix = d['prefix']
477                converter = self.value_converters.get(prefix, None)
478                if converter:
479                    suffix = d['suffix']
480                    converter = getattr(self, converter)
481                    value = converter(suffix)
482        return 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.

def configure_custom(self, config):
484    def configure_custom(self, config):
485        """Configure an object with a user-supplied factory."""
486        c = config.pop('()')
487        if not callable(c):
488            c = self.resolve(c)
489        # Check for valid identifiers
490        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
491        result = c(**kwargs)
492        props = config.pop('.', None)
493        if props:
494            for name, value in props.items():
495                setattr(result, name, value)
496        return result

Configure an object with a user-supplied factory.

def as_tuple(self, value):
498    def as_tuple(self, value):
499        """Utility function which converts lists to tuples."""
500        if isinstance(value, list):
501            value = tuple(value)
502        return value

Utility function which converts lists to tuples.

class DictConfigurator(BaseConfigurator):
531class DictConfigurator(BaseConfigurator):
532    """
533    Configure logging using a dictionary-like object to describe the
534    configuration.
535    """
536
537    def configure(self):
538        """Do the configuration."""
539
540        config = self.config
541        if 'version' not in config:
542            raise ValueError("dictionary doesn't specify a version")
543        if config['version'] != 1:
544            raise ValueError("Unsupported version: %s" % config['version'])
545        incremental = config.pop('incremental', False)
546        EMPTY_DICT = {}
547        logging._acquireLock()
548        try:
549            if incremental:
550                handlers = config.get('handlers', EMPTY_DICT)
551                for name in handlers:
552                    if name not in logging._handlers:
553                        raise ValueError('No handler found with '
554                                         'name %r'  % name)
555                    else:
556                        try:
557                            handler = logging._handlers[name]
558                            handler_config = handlers[name]
559                            level = handler_config.get('level', None)
560                            if level:
561                                handler.setLevel(logging._checkLevel(level))
562                        except Exception as e:
563                            raise ValueError('Unable to configure handler '
564                                             '%r' % name) from e
565                loggers = config.get('loggers', EMPTY_DICT)
566                for name in loggers:
567                    try:
568                        self.configure_logger(name, loggers[name], True)
569                    except Exception as e:
570                        raise ValueError('Unable to configure logger '
571                                         '%r' % name) from e
572                root = config.get('root', None)
573                if root:
574                    try:
575                        self.configure_root(root, True)
576                    except Exception as e:
577                        raise ValueError('Unable to configure root '
578                                         'logger') from e
579            else:
580                disable_existing = config.pop('disable_existing_loggers', True)
581
582                _clearExistingHandlers()
583
584                # Do formatters first - they don't refer to anything else
585                formatters = config.get('formatters', EMPTY_DICT)
586                for name in formatters:
587                    try:
588                        formatters[name] = self.configure_formatter(
589                                                            formatters[name])
590                    except Exception as e:
591                        raise ValueError('Unable to configure '
592                                         'formatter %r' % name) from e
593                # Next, do filters - they don't refer to anything else, either
594                filters = config.get('filters', EMPTY_DICT)
595                for name in filters:
596                    try:
597                        filters[name] = self.configure_filter(filters[name])
598                    except Exception as e:
599                        raise ValueError('Unable to configure '
600                                         'filter %r' % name) from e
601
602                # Next, do handlers - they refer to formatters and filters
603                # As handlers can refer to other handlers, sort the keys
604                # to allow a deterministic order of configuration
605                handlers = config.get('handlers', EMPTY_DICT)
606                deferred = []
607                for name in sorted(handlers):
608                    try:
609                        handler = self.configure_handler(handlers[name])
610                        handler.name = name
611                        handlers[name] = handler
612                    except Exception as e:
613                        if ' not configured yet' in str(e.__cause__):
614                            deferred.append(name)
615                        else:
616                            raise ValueError('Unable to configure handler '
617                                             '%r' % name) from e
618
619                # Now do any that were deferred
620                for name in deferred:
621                    try:
622                        handler = self.configure_handler(handlers[name])
623                        handler.name = name
624                        handlers[name] = handler
625                    except Exception as e:
626                        raise ValueError('Unable to configure handler '
627                                         '%r' % name) from e
628
629                # Next, do loggers - they refer to handlers and filters
630
631                #we don't want to lose the existing loggers,
632                #since other threads may have pointers to them.
633                #existing is set to contain all existing loggers,
634                #and as we go through the new configuration we
635                #remove any which are configured. At the end,
636                #what's left in existing is the set of loggers
637                #which were in the previous configuration but
638                #which are not in the new configuration.
639                root = logging.root
640                existing = list(root.manager.loggerDict.keys())
641                #The list needs to be sorted so that we can
642                #avoid disabling child loggers of explicitly
643                #named loggers. With a sorted list it is easier
644                #to find the child loggers.
645                existing.sort()
646                #We'll keep the list of existing loggers
647                #which are children of named loggers here...
648                child_loggers = []
649                #now set up the new ones...
650                loggers = config.get('loggers', EMPTY_DICT)
651                for name in loggers:
652                    if name in existing:
653                        i = existing.index(name) + 1 # look after name
654                        prefixed = name + "."
655                        pflen = len(prefixed)
656                        num_existing = len(existing)
657                        while i < num_existing:
658                            if existing[i][:pflen] == prefixed:
659                                child_loggers.append(existing[i])
660                            i += 1
661                        existing.remove(name)
662                    try:
663                        self.configure_logger(name, loggers[name])
664                    except Exception as e:
665                        raise ValueError('Unable to configure logger '
666                                         '%r' % name) from e
667
668                #Disable any old loggers. There's no point deleting
669                #them as other threads may continue to hold references
670                #and by disabling them, you stop them doing any logging.
671                #However, don't disable children of named loggers, as that's
672                #probably not what was intended by the user.
673                #for log in existing:
674                #    logger = root.manager.loggerDict[log]
675                #    if log in child_loggers:
676                #        logger.level = logging.NOTSET
677                #        logger.handlers = []
678                #        logger.propagate = True
679                #    elif disable_existing:
680                #        logger.disabled = True
681                _handle_existing_loggers(existing, child_loggers,
682                                         disable_existing)
683
684                # And finally, do the root logger
685                root = config.get('root', None)
686                if root:
687                    try:
688                        self.configure_root(root)
689                    except Exception as e:
690                        raise ValueError('Unable to configure root '
691                                         'logger') from e
692        finally:
693            logging._releaseLock()
694
695    def configure_formatter(self, config):
696        """Configure a formatter from a dictionary."""
697        if '()' in config:
698            factory = config['()'] # for use in exception handler
699            try:
700                result = self.configure_custom(config)
701            except TypeError as te:
702                if "'format'" not in str(te):
703                    raise
704                #Name of parameter changed from fmt to format.
705                #Retry with old name.
706                #This is so that code can be used with older Python versions
707                #(e.g. by Django)
708                config['fmt'] = config.pop('format')
709                config['()'] = factory
710                result = self.configure_custom(config)
711        else:
712            fmt = config.get('format', None)
713            dfmt = config.get('datefmt', None)
714            style = config.get('style', '%')
715            cname = config.get('class', None)
716            defaults = config.get('defaults', None)
717
718            if not cname:
719                c = logging.Formatter
720            else:
721                c = _resolve(cname)
722
723            kwargs  = {}
724
725            # Add defaults only if it exists.
726            # Prevents TypeError in custom formatter callables that do not
727            # accept it.
728            if defaults is not None:
729                kwargs['defaults'] = defaults
730
731            # A TypeError would be raised if "validate" key is passed in with a formatter callable
732            # that does not accept "validate" as a parameter
733            if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
734                result = c(fmt, dfmt, style, config['validate'], **kwargs)
735            else:
736                result = c(fmt, dfmt, style, **kwargs)
737
738        return result
739
740    def configure_filter(self, config):
741        """Configure a filter from a dictionary."""
742        if '()' in config:
743            result = self.configure_custom(config)
744        else:
745            name = config.get('name', '')
746            result = logging.Filter(name)
747        return result
748
749    def add_filters(self, filterer, filters):
750        """Add filters to a filterer from a list of names."""
751        for f in filters:
752            try:
753                if callable(f) or callable(getattr(f, 'filter', None)):
754                    filter_ = f
755                else:
756                    filter_ = self.config['filters'][f]
757                filterer.addFilter(filter_)
758            except Exception as e:
759                raise ValueError('Unable to add filter %r' % f) from e
760
761    def _configure_queue_handler(self, klass, **kwargs):
762        if 'queue' in kwargs:
763            q = kwargs.pop('queue')
764        else:
765            q = queue.Queue()  # unbounded
766
767        rhl = kwargs.pop('respect_handler_level', False)
768        lklass = kwargs.pop('listener', logging.handlers.QueueListener)
769        handlers = kwargs.pop('handlers', [])
770
771        listener = lklass(q, *handlers, respect_handler_level=rhl)
772        handler = klass(q, **kwargs)
773        handler.listener = listener
774        return handler
775
776    def configure_handler(self, config):
777        """Configure a handler from a dictionary."""
778        config_copy = dict(config)  # for restoring in case of error
779        formatter = config.pop('formatter', None)
780        if formatter:
781            try:
782                formatter = self.config['formatters'][formatter]
783            except Exception as e:
784                raise ValueError('Unable to set formatter '
785                                 '%r' % formatter) from e
786        level = config.pop('level', None)
787        filters = config.pop('filters', None)
788        if '()' in config:
789            c = config.pop('()')
790            if not callable(c):
791                c = self.resolve(c)
792            factory = c
793        else:
794            cname = config.pop('class')
795            if callable(cname):
796                klass = cname
797            else:
798                klass = self.resolve(cname)
799            if issubclass(klass, logging.handlers.MemoryHandler):
800                if 'flushLevel' in config:
801                    config['flushLevel'] = logging._checkLevel(config['flushLevel'])
802                if 'target' in config:
803                    # Special case for handler which refers to another handler
804                    try:
805                        tn = config['target']
806                        th = self.config['handlers'][tn]
807                        if not isinstance(th, logging.Handler):
808                            config.update(config_copy)  # restore for deferred cfg
809                            raise TypeError('target not configured yet')
810                        config['target'] = th
811                    except Exception as e:
812                        raise ValueError('Unable to set target handler %r' % tn) from e
813            elif issubclass(klass, logging.handlers.QueueHandler):
814                # Another special case for handler which refers to other handlers
815                # if 'handlers' not in config:
816                    # raise ValueError('No handlers specified for a QueueHandler')
817                if 'queue' in config:
818                    qspec = config['queue']
819
820                    if isinstance(qspec, str):
821                        q = self.resolve(qspec)
822                        if not callable(q):
823                            raise TypeError('Invalid queue specifier %r' % qspec)
824                        config['queue'] = q()
825                    elif isinstance(qspec, dict):
826                        if '()' not in qspec:
827                            raise TypeError('Invalid queue specifier %r' % qspec)
828                        config['queue'] = self.configure_custom(dict(qspec))
829                    elif not _is_queue_like_object(qspec):
830                        raise TypeError('Invalid queue specifier %r' % qspec)
831
832                if 'listener' in config:
833                    lspec = config['listener']
834                    if isinstance(lspec, type):
835                        if not issubclass(lspec, logging.handlers.QueueListener):
836                            raise TypeError('Invalid listener specifier %r' % lspec)
837                    else:
838                        if isinstance(lspec, str):
839                            listener = self.resolve(lspec)
840                            if isinstance(listener, type) and\
841                                not issubclass(listener, logging.handlers.QueueListener):
842                                raise TypeError('Invalid listener specifier %r' % lspec)
843                        elif isinstance(lspec, dict):
844                            if '()' not in lspec:
845                                raise TypeError('Invalid listener specifier %r' % lspec)
846                            listener = self.configure_custom(dict(lspec))
847                        else:
848                            raise TypeError('Invalid listener specifier %r' % lspec)
849                        if not callable(listener):
850                            raise TypeError('Invalid listener specifier %r' % lspec)
851                        config['listener'] = listener
852                if 'handlers' in config:
853                    hlist = []
854                    try:
855                        for hn in config['handlers']:
856                            h = self.config['handlers'][hn]
857                            if not isinstance(h, logging.Handler):
858                                config.update(config_copy)  # restore for deferred cfg
859                                raise TypeError('Required handler %r '
860                                                'is not configured yet' % hn)
861                            hlist.append(h)
862                    except Exception as e:
863                        raise ValueError('Unable to set required handler %r' % hn) from e
864                    config['handlers'] = hlist
865            elif issubclass(klass, logging.handlers.SMTPHandler) and\
866                'mailhost' in config:
867                config['mailhost'] = self.as_tuple(config['mailhost'])
868            elif issubclass(klass, logging.handlers.SysLogHandler) and\
869                'address' in config:
870                config['address'] = self.as_tuple(config['address'])
871            if issubclass(klass, logging.handlers.QueueHandler):
872                factory = functools.partial(self._configure_queue_handler, klass)
873            else:
874                factory = klass
875        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
876        try:
877            result = factory(**kwargs)
878        except TypeError as te:
879            if "'stream'" not in str(te):
880                raise
881            #The argument name changed from strm to stream
882            #Retry with old name.
883            #This is so that code can be used with older Python versions
884            #(e.g. by Django)
885            kwargs['strm'] = kwargs.pop('stream')
886            result = factory(**kwargs)
887        if formatter:
888            result.setFormatter(formatter)
889        if level is not None:
890            result.setLevel(logging._checkLevel(level))
891        if filters:
892            self.add_filters(result, filters)
893        props = config.pop('.', None)
894        if props:
895            for name, value in props.items():
896                setattr(result, name, value)
897        return result
898
899    def add_handlers(self, logger, handlers):
900        """Add handlers to a logger from a list of names."""
901        for h in handlers:
902            try:
903                logger.addHandler(self.config['handlers'][h])
904            except Exception as e:
905                raise ValueError('Unable to add handler %r' % h) from e
906
907    def common_logger_config(self, logger, config, incremental=False):
908        """
909        Perform configuration which is common to root and non-root loggers.
910        """
911        level = config.get('level', None)
912        if level is not None:
913            logger.setLevel(logging._checkLevel(level))
914        if not incremental:
915            #Remove any existing handlers
916            for h in logger.handlers[:]:
917                logger.removeHandler(h)
918            handlers = config.get('handlers', None)
919            if handlers:
920                self.add_handlers(logger, handlers)
921            filters = config.get('filters', None)
922            if filters:
923                self.add_filters(logger, filters)
924
925    def configure_logger(self, name, config, incremental=False):
926        """Configure a non-root logger from a dictionary."""
927        logger = logging.getLogger(name)
928        self.common_logger_config(logger, config, incremental)
929        logger.disabled = False
930        propagate = config.get('propagate', None)
931        if propagate is not None:
932            logger.propagate = propagate
933
934    def configure_root(self, config, incremental=False):
935        """Configure a root logger from a dictionary."""
936        root = logging.getLogger()
937        self.common_logger_config(root, config, incremental)

Configure logging using a dictionary-like object to describe the configuration.

def configure(self):
537    def configure(self):
538        """Do the configuration."""
539
540        config = self.config
541        if 'version' not in config:
542            raise ValueError("dictionary doesn't specify a version")
543        if config['version'] != 1:
544            raise ValueError("Unsupported version: %s" % config['version'])
545        incremental = config.pop('incremental', False)
546        EMPTY_DICT = {}
547        logging._acquireLock()
548        try:
549            if incremental:
550                handlers = config.get('handlers', EMPTY_DICT)
551                for name in handlers:
552                    if name not in logging._handlers:
553                        raise ValueError('No handler found with '
554                                         'name %r'  % name)
555                    else:
556                        try:
557                            handler = logging._handlers[name]
558                            handler_config = handlers[name]
559                            level = handler_config.get('level', None)
560                            if level:
561                                handler.setLevel(logging._checkLevel(level))
562                        except Exception as e:
563                            raise ValueError('Unable to configure handler '
564                                             '%r' % name) from e
565                loggers = config.get('loggers', EMPTY_DICT)
566                for name in loggers:
567                    try:
568                        self.configure_logger(name, loggers[name], True)
569                    except Exception as e:
570                        raise ValueError('Unable to configure logger '
571                                         '%r' % name) from e
572                root = config.get('root', None)
573                if root:
574                    try:
575                        self.configure_root(root, True)
576                    except Exception as e:
577                        raise ValueError('Unable to configure root '
578                                         'logger') from e
579            else:
580                disable_existing = config.pop('disable_existing_loggers', True)
581
582                _clearExistingHandlers()
583
584                # Do formatters first - they don't refer to anything else
585                formatters = config.get('formatters', EMPTY_DICT)
586                for name in formatters:
587                    try:
588                        formatters[name] = self.configure_formatter(
589                                                            formatters[name])
590                    except Exception as e:
591                        raise ValueError('Unable to configure '
592                                         'formatter %r' % name) from e
593                # Next, do filters - they don't refer to anything else, either
594                filters = config.get('filters', EMPTY_DICT)
595                for name in filters:
596                    try:
597                        filters[name] = self.configure_filter(filters[name])
598                    except Exception as e:
599                        raise ValueError('Unable to configure '
600                                         'filter %r' % name) from e
601
602                # Next, do handlers - they refer to formatters and filters
603                # As handlers can refer to other handlers, sort the keys
604                # to allow a deterministic order of configuration
605                handlers = config.get('handlers', EMPTY_DICT)
606                deferred = []
607                for name in sorted(handlers):
608                    try:
609                        handler = self.configure_handler(handlers[name])
610                        handler.name = name
611                        handlers[name] = handler
612                    except Exception as e:
613                        if ' not configured yet' in str(e.__cause__):
614                            deferred.append(name)
615                        else:
616                            raise ValueError('Unable to configure handler '
617                                             '%r' % name) from e
618
619                # Now do any that were deferred
620                for name in deferred:
621                    try:
622                        handler = self.configure_handler(handlers[name])
623                        handler.name = name
624                        handlers[name] = handler
625                    except Exception as e:
626                        raise ValueError('Unable to configure handler '
627                                         '%r' % name) from e
628
629                # Next, do loggers - they refer to handlers and filters
630
631                #we don't want to lose the existing loggers,
632                #since other threads may have pointers to them.
633                #existing is set to contain all existing loggers,
634                #and as we go through the new configuration we
635                #remove any which are configured. At the end,
636                #what's left in existing is the set of loggers
637                #which were in the previous configuration but
638                #which are not in the new configuration.
639                root = logging.root
640                existing = list(root.manager.loggerDict.keys())
641                #The list needs to be sorted so that we can
642                #avoid disabling child loggers of explicitly
643                #named loggers. With a sorted list it is easier
644                #to find the child loggers.
645                existing.sort()
646                #We'll keep the list of existing loggers
647                #which are children of named loggers here...
648                child_loggers = []
649                #now set up the new ones...
650                loggers = config.get('loggers', EMPTY_DICT)
651                for name in loggers:
652                    if name in existing:
653                        i = existing.index(name) + 1 # look after name
654                        prefixed = name + "."
655                        pflen = len(prefixed)
656                        num_existing = len(existing)
657                        while i < num_existing:
658                            if existing[i][:pflen] == prefixed:
659                                child_loggers.append(existing[i])
660                            i += 1
661                        existing.remove(name)
662                    try:
663                        self.configure_logger(name, loggers[name])
664                    except Exception as e:
665                        raise ValueError('Unable to configure logger '
666                                         '%r' % name) from e
667
668                #Disable any old loggers. There's no point deleting
669                #them as other threads may continue to hold references
670                #and by disabling them, you stop them doing any logging.
671                #However, don't disable children of named loggers, as that's
672                #probably not what was intended by the user.
673                #for log in existing:
674                #    logger = root.manager.loggerDict[log]
675                #    if log in child_loggers:
676                #        logger.level = logging.NOTSET
677                #        logger.handlers = []
678                #        logger.propagate = True
679                #    elif disable_existing:
680                #        logger.disabled = True
681                _handle_existing_loggers(existing, child_loggers,
682                                         disable_existing)
683
684                # And finally, do the root logger
685                root = config.get('root', None)
686                if root:
687                    try:
688                        self.configure_root(root)
689                    except Exception as e:
690                        raise ValueError('Unable to configure root '
691                                         'logger') from e
692        finally:
693            logging._releaseLock()

Do the configuration.

def configure_formatter(self, config):
695    def configure_formatter(self, config):
696        """Configure a formatter from a dictionary."""
697        if '()' in config:
698            factory = config['()'] # for use in exception handler
699            try:
700                result = self.configure_custom(config)
701            except TypeError as te:
702                if "'format'" not in str(te):
703                    raise
704                #Name of parameter changed from fmt to format.
705                #Retry with old name.
706                #This is so that code can be used with older Python versions
707                #(e.g. by Django)
708                config['fmt'] = config.pop('format')
709                config['()'] = factory
710                result = self.configure_custom(config)
711        else:
712            fmt = config.get('format', None)
713            dfmt = config.get('datefmt', None)
714            style = config.get('style', '%')
715            cname = config.get('class', None)
716            defaults = config.get('defaults', None)
717
718            if not cname:
719                c = logging.Formatter
720            else:
721                c = _resolve(cname)
722
723            kwargs  = {}
724
725            # Add defaults only if it exists.
726            # Prevents TypeError in custom formatter callables that do not
727            # accept it.
728            if defaults is not None:
729                kwargs['defaults'] = defaults
730
731            # A TypeError would be raised if "validate" key is passed in with a formatter callable
732            # that does not accept "validate" as a parameter
733            if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
734                result = c(fmt, dfmt, style, config['validate'], **kwargs)
735            else:
736                result = c(fmt, dfmt, style, **kwargs)
737
738        return result

Configure a formatter from a dictionary.

def configure_filter(self, config):
740    def configure_filter(self, config):
741        """Configure a filter from a dictionary."""
742        if '()' in config:
743            result = self.configure_custom(config)
744        else:
745            name = config.get('name', '')
746            result = logging.Filter(name)
747        return result

Configure a filter from a dictionary.

def add_filters(self, filterer, filters):
749    def add_filters(self, filterer, filters):
750        """Add filters to a filterer from a list of names."""
751        for f in filters:
752            try:
753                if callable(f) or callable(getattr(f, 'filter', None)):
754                    filter_ = f
755                else:
756                    filter_ = self.config['filters'][f]
757                filterer.addFilter(filter_)
758            except Exception as e:
759                raise ValueError('Unable to add filter %r' % f) from e

Add filters to a filterer from a list of names.

def configure_handler(self, config):
776    def configure_handler(self, config):
777        """Configure a handler from a dictionary."""
778        config_copy = dict(config)  # for restoring in case of error
779        formatter = config.pop('formatter', None)
780        if formatter:
781            try:
782                formatter = self.config['formatters'][formatter]
783            except Exception as e:
784                raise ValueError('Unable to set formatter '
785                                 '%r' % formatter) from e
786        level = config.pop('level', None)
787        filters = config.pop('filters', None)
788        if '()' in config:
789            c = config.pop('()')
790            if not callable(c):
791                c = self.resolve(c)
792            factory = c
793        else:
794            cname = config.pop('class')
795            if callable(cname):
796                klass = cname
797            else:
798                klass = self.resolve(cname)
799            if issubclass(klass, logging.handlers.MemoryHandler):
800                if 'flushLevel' in config:
801                    config['flushLevel'] = logging._checkLevel(config['flushLevel'])
802                if 'target' in config:
803                    # Special case for handler which refers to another handler
804                    try:
805                        tn = config['target']
806                        th = self.config['handlers'][tn]
807                        if not isinstance(th, logging.Handler):
808                            config.update(config_copy)  # restore for deferred cfg
809                            raise TypeError('target not configured yet')
810                        config['target'] = th
811                    except Exception as e:
812                        raise ValueError('Unable to set target handler %r' % tn) from e
813            elif issubclass(klass, logging.handlers.QueueHandler):
814                # Another special case for handler which refers to other handlers
815                # if 'handlers' not in config:
816                    # raise ValueError('No handlers specified for a QueueHandler')
817                if 'queue' in config:
818                    qspec = config['queue']
819
820                    if isinstance(qspec, str):
821                        q = self.resolve(qspec)
822                        if not callable(q):
823                            raise TypeError('Invalid queue specifier %r' % qspec)
824                        config['queue'] = q()
825                    elif isinstance(qspec, dict):
826                        if '()' not in qspec:
827                            raise TypeError('Invalid queue specifier %r' % qspec)
828                        config['queue'] = self.configure_custom(dict(qspec))
829                    elif not _is_queue_like_object(qspec):
830                        raise TypeError('Invalid queue specifier %r' % qspec)
831
832                if 'listener' in config:
833                    lspec = config['listener']
834                    if isinstance(lspec, type):
835                        if not issubclass(lspec, logging.handlers.QueueListener):
836                            raise TypeError('Invalid listener specifier %r' % lspec)
837                    else:
838                        if isinstance(lspec, str):
839                            listener = self.resolve(lspec)
840                            if isinstance(listener, type) and\
841                                not issubclass(listener, logging.handlers.QueueListener):
842                                raise TypeError('Invalid listener specifier %r' % lspec)
843                        elif isinstance(lspec, dict):
844                            if '()' not in lspec:
845                                raise TypeError('Invalid listener specifier %r' % lspec)
846                            listener = self.configure_custom(dict(lspec))
847                        else:
848                            raise TypeError('Invalid listener specifier %r' % lspec)
849                        if not callable(listener):
850                            raise TypeError('Invalid listener specifier %r' % lspec)
851                        config['listener'] = listener
852                if 'handlers' in config:
853                    hlist = []
854                    try:
855                        for hn in config['handlers']:
856                            h = self.config['handlers'][hn]
857                            if not isinstance(h, logging.Handler):
858                                config.update(config_copy)  # restore for deferred cfg
859                                raise TypeError('Required handler %r '
860                                                'is not configured yet' % hn)
861                            hlist.append(h)
862                    except Exception as e:
863                        raise ValueError('Unable to set required handler %r' % hn) from e
864                    config['handlers'] = hlist
865            elif issubclass(klass, logging.handlers.SMTPHandler) and\
866                'mailhost' in config:
867                config['mailhost'] = self.as_tuple(config['mailhost'])
868            elif issubclass(klass, logging.handlers.SysLogHandler) and\
869                'address' in config:
870                config['address'] = self.as_tuple(config['address'])
871            if issubclass(klass, logging.handlers.QueueHandler):
872                factory = functools.partial(self._configure_queue_handler, klass)
873            else:
874                factory = klass
875        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
876        try:
877            result = factory(**kwargs)
878        except TypeError as te:
879            if "'stream'" not in str(te):
880                raise
881            #The argument name changed from strm to stream
882            #Retry with old name.
883            #This is so that code can be used with older Python versions
884            #(e.g. by Django)
885            kwargs['strm'] = kwargs.pop('stream')
886            result = factory(**kwargs)
887        if formatter:
888            result.setFormatter(formatter)
889        if level is not None:
890            result.setLevel(logging._checkLevel(level))
891        if filters:
892            self.add_filters(result, filters)
893        props = config.pop('.', None)
894        if props:
895            for name, value in props.items():
896                setattr(result, name, value)
897        return result

Configure a handler from a dictionary.

def add_handlers(self, logger, handlers):
899    def add_handlers(self, logger, handlers):
900        """Add handlers to a logger from a list of names."""
901        for h in handlers:
902            try:
903                logger.addHandler(self.config['handlers'][h])
904            except Exception as e:
905                raise ValueError('Unable to add handler %r' % h) from e

Add handlers to a logger from a list of names.

def common_logger_config(self, logger, config, incremental=False):
907    def common_logger_config(self, logger, config, incremental=False):
908        """
909        Perform configuration which is common to root and non-root loggers.
910        """
911        level = config.get('level', None)
912        if level is not None:
913            logger.setLevel(logging._checkLevel(level))
914        if not incremental:
915            #Remove any existing handlers
916            for h in logger.handlers[:]:
917                logger.removeHandler(h)
918            handlers = config.get('handlers', None)
919            if handlers:
920                self.add_handlers(logger, handlers)
921            filters = config.get('filters', None)
922            if filters:
923                self.add_filters(logger, filters)

Perform configuration which is common to root and non-root loggers.

def configure_logger(self, name, config, incremental=False):
925    def configure_logger(self, name, config, incremental=False):
926        """Configure a non-root logger from a dictionary."""
927        logger = logging.getLogger(name)
928        self.common_logger_config(logger, config, incremental)
929        logger.disabled = False
930        propagate = config.get('propagate', None)
931        if propagate is not None:
932            logger.propagate = propagate

Configure a non-root logger from a dictionary.

def configure_root(self, config, incremental=False):
934    def configure_root(self, config, incremental=False):
935        """Configure a root logger from a dictionary."""
936        root = logging.getLogger()
937        self.common_logger_config(root, config, incremental)

Configure a root logger from a dictionary.

dictConfigClass = <class 'DictConfigurator'>
def dictConfig(config):
941def dictConfig(config):
942    """Configure logging using a dictionary."""
943    dictConfigClass(config).configure()

Configure logging using a dictionary.

def listen(port=9030, verify=None):
 946def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
 947    """
 948    Start up a socket server on the specified port, and listen for new
 949    configurations.
 950
 951    These will be sent as a file suitable for processing by fileConfig().
 952    Returns a Thread object on which you can call start() to start the server,
 953    and which you can join() when appropriate. To stop the server, call
 954    stopListening().
 955
 956    Use the ``verify`` argument to verify any bytes received across the wire
 957    from a client. If specified, it should be a callable which receives a
 958    single argument - the bytes of configuration data received across the
 959    network - and it should return either ``None``, to indicate that the
 960    passed in bytes could not be verified and should be discarded, or a
 961    byte string which is then passed to the configuration machinery as
 962    normal. Note that you can return transformed bytes, e.g. by decrypting
 963    the bytes passed in.
 964    """
 965
 966    class ConfigStreamHandler(StreamRequestHandler):
 967        """
 968        Handler for a logging configuration request.
 969
 970        It expects a completely new logging configuration and uses fileConfig
 971        to install it.
 972        """
 973        def handle(self):
 974            """
 975            Handle a request.
 976
 977            Each request is expected to be a 4-byte length, packed using
 978            struct.pack(">L", n), followed by the config file.
 979            Uses fileConfig() to do the grunt work.
 980            """
 981            try:
 982                conn = self.connection
 983                chunk = conn.recv(4)
 984                if len(chunk) == 4:
 985                    slen = struct.unpack(">L", chunk)[0]
 986                    chunk = self.connection.recv(slen)
 987                    while len(chunk) < slen:
 988                        chunk = chunk + conn.recv(slen - len(chunk))
 989                    if self.server.verify is not None:
 990                        chunk = self.server.verify(chunk)
 991                    if chunk is not None:   # verified, can process
 992                        chunk = chunk.decode("utf-8")
 993                        try:
 994                            import json
 995                            d =json.loads(chunk)
 996                            assert isinstance(d, dict)
 997                            dictConfig(d)
 998                        except Exception:
 999                            #Apply new configuration.
1000
1001                            file = io.StringIO(chunk)
1002                            try:
1003                                fileConfig(file)
1004                            except Exception:
1005                                traceback.print_exc()
1006                    if self.server.ready:
1007                        self.server.ready.set()
1008            except OSError as e:
1009                if e.errno != RESET_ERROR:
1010                    raise
1011
1012    class ConfigSocketReceiver(ThreadingTCPServer):
1013        """
1014        A simple TCP socket-based logging config receiver.
1015        """
1016
1017        allow_reuse_address = 1
1018
1019        def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
1020                     handler=None, ready=None, verify=None):
1021            ThreadingTCPServer.__init__(self, (host, port), handler)
1022            logging._acquireLock()
1023            self.abort = 0
1024            logging._releaseLock()
1025            self.timeout = 1
1026            self.ready = ready
1027            self.verify = verify
1028
1029        def serve_until_stopped(self):
1030            import select
1031            abort = 0
1032            while not abort:
1033                rd, wr, ex = select.select([self.socket.fileno()],
1034                                           [], [],
1035                                           self.timeout)
1036                if rd:
1037                    self.handle_request()
1038                logging._acquireLock()
1039                abort = self.abort
1040                logging._releaseLock()
1041            self.server_close()
1042
1043    class Server(threading.Thread):
1044
1045        def __init__(self, rcvr, hdlr, port, verify):
1046            super(Server, self).__init__()
1047            self.rcvr = rcvr
1048            self.hdlr = hdlr
1049            self.port = port
1050            self.verify = verify
1051            self.ready = threading.Event()
1052
1053        def run(self):
1054            server = self.rcvr(port=self.port, handler=self.hdlr,
1055                               ready=self.ready,
1056                               verify=self.verify)
1057            if self.port == 0:
1058                self.port = server.server_address[1]
1059            self.ready.set()
1060            global _listener
1061            logging._acquireLock()
1062            _listener = server
1063            logging._releaseLock()
1064            server.serve_until_stopped()
1065
1066    return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify)

Start up a socket server on the specified port, and listen for new configurations.

These will be sent as a file suitable for processing by fileConfig(). Returns a Thread object on which you can call start() to start the server, and which you can join() when appropriate. To stop the server, call stopListening().

Use the verify argument to verify any bytes received across the wire from a client. If specified, it should be a callable which receives a single argument - the bytes of configuration data received across the network - and it should return either None, to indicate that the passed in bytes could not be verified and should be discarded, or a byte string which is then passed to the configuration machinery as normal. Note that you can return transformed bytes, e.g. by decrypting the bytes passed in.

def stopListening():
1068def stopListening():
1069    """
1070    Stop the listening server which was created with a call to listen().
1071    """
1072    global _listener
1073    logging._acquireLock()
1074    try:
1075        if _listener:
1076            _listener.abort = 1
1077            _listener = None
1078    finally:
1079        logging._releaseLock()

Stop the listening server which was created with a call to listen().