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
 503class DictConfigurator(BaseConfigurator):
 504    """
 505    Configure logging using a dictionary-like object to describe the
 506    configuration.
 507    """
 508
 509    def configure(self):
 510        """Do the configuration."""
 511
 512        config = self.config
 513        if 'version' not in config:
 514            raise ValueError("dictionary doesn't specify a version")
 515        if config['version'] != 1:
 516            raise ValueError("Unsupported version: %s" % config['version'])
 517        incremental = config.pop('incremental', False)
 518        EMPTY_DICT = {}
 519        logging._acquireLock()
 520        try:
 521            if incremental:
 522                handlers = config.get('handlers', EMPTY_DICT)
 523                for name in handlers:
 524                    if name not in logging._handlers:
 525                        raise ValueError('No handler found with '
 526                                         'name %r'  % name)
 527                    else:
 528                        try:
 529                            handler = logging._handlers[name]
 530                            handler_config = handlers[name]
 531                            level = handler_config.get('level', None)
 532                            if level:
 533                                handler.setLevel(logging._checkLevel(level))
 534                        except Exception as e:
 535                            raise ValueError('Unable to configure handler '
 536                                             '%r' % name) from e
 537                loggers = config.get('loggers', EMPTY_DICT)
 538                for name in loggers:
 539                    try:
 540                        self.configure_logger(name, loggers[name], True)
 541                    except Exception as e:
 542                        raise ValueError('Unable to configure logger '
 543                                         '%r' % name) from e
 544                root = config.get('root', None)
 545                if root:
 546                    try:
 547                        self.configure_root(root, True)
 548                    except Exception as e:
 549                        raise ValueError('Unable to configure root '
 550                                         'logger') from e
 551            else:
 552                disable_existing = config.pop('disable_existing_loggers', True)
 553
 554                _clearExistingHandlers()
 555
 556                # Do formatters first - they don't refer to anything else
 557                formatters = config.get('formatters', EMPTY_DICT)
 558                for name in formatters:
 559                    try:
 560                        formatters[name] = self.configure_formatter(
 561                                                            formatters[name])
 562                    except Exception as e:
 563                        raise ValueError('Unable to configure '
 564                                         'formatter %r' % name) from e
 565                # Next, do filters - they don't refer to anything else, either
 566                filters = config.get('filters', EMPTY_DICT)
 567                for name in filters:
 568                    try:
 569                        filters[name] = self.configure_filter(filters[name])
 570                    except Exception as e:
 571                        raise ValueError('Unable to configure '
 572                                         'filter %r' % name) from e
 573
 574                # Next, do handlers - they refer to formatters and filters
 575                # As handlers can refer to other handlers, sort the keys
 576                # to allow a deterministic order of configuration
 577                handlers = config.get('handlers', EMPTY_DICT)
 578                deferred = []
 579                for name in sorted(handlers):
 580                    try:
 581                        handler = self.configure_handler(handlers[name])
 582                        handler.name = name
 583                        handlers[name] = handler
 584                    except Exception as e:
 585                        if ' not configured yet' in str(e.__cause__):
 586                            deferred.append(name)
 587                        else:
 588                            raise ValueError('Unable to configure handler '
 589                                             '%r' % name) from e
 590
 591                # Now do any that were deferred
 592                for name in deferred:
 593                    try:
 594                        handler = self.configure_handler(handlers[name])
 595                        handler.name = name
 596                        handlers[name] = handler
 597                    except Exception as e:
 598                        raise ValueError('Unable to configure handler '
 599                                         '%r' % name) from e
 600
 601                # Next, do loggers - they refer to handlers and filters
 602
 603                #we don't want to lose the existing loggers,
 604                #since other threads may have pointers to them.
 605                #existing is set to contain all existing loggers,
 606                #and as we go through the new configuration we
 607                #remove any which are configured. At the end,
 608                #what's left in existing is the set of loggers
 609                #which were in the previous configuration but
 610                #which are not in the new configuration.
 611                root = logging.root
 612                existing = list(root.manager.loggerDict.keys())
 613                #The list needs to be sorted so that we can
 614                #avoid disabling child loggers of explicitly
 615                #named loggers. With a sorted list it is easier
 616                #to find the child loggers.
 617                existing.sort()
 618                #We'll keep the list of existing loggers
 619                #which are children of named loggers here...
 620                child_loggers = []
 621                #now set up the new ones...
 622                loggers = config.get('loggers', EMPTY_DICT)
 623                for name in loggers:
 624                    if name in existing:
 625                        i = existing.index(name) + 1 # look after name
 626                        prefixed = name + "."
 627                        pflen = len(prefixed)
 628                        num_existing = len(existing)
 629                        while i < num_existing:
 630                            if existing[i][:pflen] == prefixed:
 631                                child_loggers.append(existing[i])
 632                            i += 1
 633                        existing.remove(name)
 634                    try:
 635                        self.configure_logger(name, loggers[name])
 636                    except Exception as e:
 637                        raise ValueError('Unable to configure logger '
 638                                         '%r' % name) from e
 639
 640                #Disable any old loggers. There's no point deleting
 641                #them as other threads may continue to hold references
 642                #and by disabling them, you stop them doing any logging.
 643                #However, don't disable children of named loggers, as that's
 644                #probably not what was intended by the user.
 645                #for log in existing:
 646                #    logger = root.manager.loggerDict[log]
 647                #    if log in child_loggers:
 648                #        logger.level = logging.NOTSET
 649                #        logger.handlers = []
 650                #        logger.propagate = True
 651                #    elif disable_existing:
 652                #        logger.disabled = True
 653                _handle_existing_loggers(existing, child_loggers,
 654                                         disable_existing)
 655
 656                # And finally, do the root logger
 657                root = config.get('root', None)
 658                if root:
 659                    try:
 660                        self.configure_root(root)
 661                    except Exception as e:
 662                        raise ValueError('Unable to configure root '
 663                                         'logger') from e
 664        finally:
 665            logging._releaseLock()
 666
 667    def configure_formatter(self, config):
 668        """Configure a formatter from a dictionary."""
 669        if '()' in config:
 670            factory = config['()'] # for use in exception handler
 671            try:
 672                result = self.configure_custom(config)
 673            except TypeError as te:
 674                if "'format'" not in str(te):
 675                    raise
 676                #Name of parameter changed from fmt to format.
 677                #Retry with old name.
 678                #This is so that code can be used with older Python versions
 679                #(e.g. by Django)
 680                config['fmt'] = config.pop('format')
 681                config['()'] = factory
 682                result = self.configure_custom(config)
 683        else:
 684            fmt = config.get('format', None)
 685            dfmt = config.get('datefmt', None)
 686            style = config.get('style', '%')
 687            cname = config.get('class', None)
 688            defaults = config.get('defaults', None)
 689
 690            if not cname:
 691                c = logging.Formatter
 692            else:
 693                c = _resolve(cname)
 694
 695            kwargs  = {}
 696
 697            # Add defaults only if it exists.
 698            # Prevents TypeError in custom formatter callables that do not
 699            # accept it.
 700            if defaults is not None:
 701                kwargs['defaults'] = defaults
 702
 703            # A TypeError would be raised if "validate" key is passed in with a formatter callable
 704            # that does not accept "validate" as a parameter
 705            if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
 706                result = c(fmt, dfmt, style, config['validate'], **kwargs)
 707            else:
 708                result = c(fmt, dfmt, style, **kwargs)
 709
 710        return result
 711
 712    def configure_filter(self, config):
 713        """Configure a filter from a dictionary."""
 714        if '()' in config:
 715            result = self.configure_custom(config)
 716        else:
 717            name = config.get('name', '')
 718            result = logging.Filter(name)
 719        return result
 720
 721    def add_filters(self, filterer, filters):
 722        """Add filters to a filterer from a list of names."""
 723        for f in filters:
 724            try:
 725                if callable(f) or callable(getattr(f, 'filter', None)):
 726                    filter_ = f
 727                else:
 728                    filter_ = self.config['filters'][f]
 729                filterer.addFilter(filter_)
 730            except Exception as e:
 731                raise ValueError('Unable to add filter %r' % f) from e
 732
 733    def _configure_queue_handler(self, klass, **kwargs):
 734        if 'queue' in kwargs:
 735            q = kwargs['queue']
 736        else:
 737            q = queue.Queue()  # unbounded
 738        rhl = kwargs.get('respect_handler_level', False)
 739        if 'listener' in kwargs:
 740            lklass = kwargs['listener']
 741        else:
 742            lklass = logging.handlers.QueueListener
 743        listener = lklass(q, *kwargs.get('handlers', []), respect_handler_level=rhl)
 744        handler = klass(q)
 745        handler.listener = listener
 746        return handler
 747
 748    def configure_handler(self, config):
 749        """Configure a handler from a dictionary."""
 750        config_copy = dict(config)  # for restoring in case of error
 751        formatter = config.pop('formatter', None)
 752        if formatter:
 753            try:
 754                formatter = self.config['formatters'][formatter]
 755            except Exception as e:
 756                raise ValueError('Unable to set formatter '
 757                                 '%r' % formatter) from e
 758        level = config.pop('level', None)
 759        filters = config.pop('filters', None)
 760        if '()' in config:
 761            c = config.pop('()')
 762            if not callable(c):
 763                c = self.resolve(c)
 764            factory = c
 765        else:
 766            cname = config.pop('class')
 767            if callable(cname):
 768                klass = cname
 769            else:
 770                klass = self.resolve(cname)
 771            if issubclass(klass, logging.handlers.MemoryHandler) and\
 772                'target' in config:
 773                # Special case for handler which refers to another handler
 774                try:
 775                    tn = config['target']
 776                    th = self.config['handlers'][tn]
 777                    if not isinstance(th, logging.Handler):
 778                        config.update(config_copy)  # restore for deferred cfg
 779                        raise TypeError('target not configured yet')
 780                    config['target'] = th
 781                except Exception as e:
 782                    raise ValueError('Unable to set target handler %r' % tn) from e
 783            elif issubclass(klass, logging.handlers.QueueHandler):
 784                # Another special case for handler which refers to other handlers
 785                # if 'handlers' not in config:
 786                    # raise ValueError('No handlers specified for a QueueHandler')
 787                if 'queue' in config:
 788                    from multiprocessing.queues import Queue as MPQueue
 789                    qspec = config['queue']
 790                    if not isinstance(qspec, (queue.Queue, MPQueue)):
 791                        if isinstance(qspec, str):
 792                            q = self.resolve(qspec)
 793                            if not callable(q):
 794                                raise TypeError('Invalid queue specifier %r' % qspec)
 795                            q = q()
 796                        elif isinstance(qspec, dict):
 797                            if '()' not in qspec:
 798                                raise TypeError('Invalid queue specifier %r' % qspec)
 799                            q = self.configure_custom(dict(qspec))
 800                        else:
 801                            raise TypeError('Invalid queue specifier %r' % qspec)
 802                        config['queue'] = q
 803                if 'listener' in config:
 804                    lspec = config['listener']
 805                    if isinstance(lspec, type):
 806                        if not issubclass(lspec, logging.handlers.QueueListener):
 807                            raise TypeError('Invalid listener specifier %r' % lspec)
 808                    else:
 809                        if isinstance(lspec, str):
 810                            listener = self.resolve(lspec)
 811                            if isinstance(listener, type) and\
 812                                not issubclass(listener, logging.handlers.QueueListener):
 813                                raise TypeError('Invalid listener specifier %r' % lspec)
 814                        elif isinstance(lspec, dict):
 815                            if '()' not in lspec:
 816                                raise TypeError('Invalid listener specifier %r' % lspec)
 817                            listener = self.configure_custom(dict(lspec))
 818                        else:
 819                            raise TypeError('Invalid listener specifier %r' % lspec)
 820                        if not callable(listener):
 821                            raise TypeError('Invalid listener specifier %r' % lspec)
 822                        config['listener'] = listener
 823                if 'handlers' in config:
 824                    hlist = []
 825                    try:
 826                        for hn in config['handlers']:
 827                            h = self.config['handlers'][hn]
 828                            if not isinstance(h, logging.Handler):
 829                                config.update(config_copy)  # restore for deferred cfg
 830                                raise TypeError('Required handler %r '
 831                                                'is not configured yet' % hn)
 832                            hlist.append(h)
 833                    except Exception as e:
 834                        raise ValueError('Unable to set required handler %r' % hn) from e
 835                    config['handlers'] = hlist
 836            elif issubclass(klass, logging.handlers.SMTPHandler) and\
 837                'mailhost' in config:
 838                config['mailhost'] = self.as_tuple(config['mailhost'])
 839            elif issubclass(klass, logging.handlers.SysLogHandler) and\
 840                'address' in config:
 841                config['address'] = self.as_tuple(config['address'])
 842            if issubclass(klass, logging.handlers.QueueHandler):
 843                factory = functools.partial(self._configure_queue_handler, klass)
 844            else:
 845                factory = klass
 846        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
 847        try:
 848            result = factory(**kwargs)
 849        except TypeError as te:
 850            if "'stream'" not in str(te):
 851                raise
 852            #The argument name changed from strm to stream
 853            #Retry with old name.
 854            #This is so that code can be used with older Python versions
 855            #(e.g. by Django)
 856            kwargs['strm'] = kwargs.pop('stream')
 857            result = factory(**kwargs)
 858        if formatter:
 859            result.setFormatter(formatter)
 860        if level is not None:
 861            result.setLevel(logging._checkLevel(level))
 862        if filters:
 863            self.add_filters(result, filters)
 864        props = config.pop('.', None)
 865        if props:
 866            for name, value in props.items():
 867                setattr(result, name, value)
 868        return result
 869
 870    def add_handlers(self, logger, handlers):
 871        """Add handlers to a logger from a list of names."""
 872        for h in handlers:
 873            try:
 874                logger.addHandler(self.config['handlers'][h])
 875            except Exception as e:
 876                raise ValueError('Unable to add handler %r' % h) from e
 877
 878    def common_logger_config(self, logger, config, incremental=False):
 879        """
 880        Perform configuration which is common to root and non-root loggers.
 881        """
 882        level = config.get('level', None)
 883        if level is not None:
 884            logger.setLevel(logging._checkLevel(level))
 885        if not incremental:
 886            #Remove any existing handlers
 887            for h in logger.handlers[:]:
 888                logger.removeHandler(h)
 889            handlers = config.get('handlers', None)
 890            if handlers:
 891                self.add_handlers(logger, handlers)
 892            filters = config.get('filters', None)
 893            if filters:
 894                self.add_filters(logger, filters)
 895
 896    def configure_logger(self, name, config, incremental=False):
 897        """Configure a non-root logger from a dictionary."""
 898        logger = logging.getLogger(name)
 899        self.common_logger_config(logger, config, incremental)
 900        logger.disabled = False
 901        propagate = config.get('propagate', None)
 902        if propagate is not None:
 903            logger.propagate = propagate
 904
 905    def configure_root(self, config, incremental=False):
 906        """Configure a root logger from a dictionary."""
 907        root = logging.getLogger()
 908        self.common_logger_config(root, config, incremental)
 909
 910dictConfigClass = DictConfigurator
 911
 912def dictConfig(config):
 913    """Configure logging using a dictionary."""
 914    dictConfigClass(config).configure()
 915
 916
 917def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
 918    """
 919    Start up a socket server on the specified port, and listen for new
 920    configurations.
 921
 922    These will be sent as a file suitable for processing by fileConfig().
 923    Returns a Thread object on which you can call start() to start the server,
 924    and which you can join() when appropriate. To stop the server, call
 925    stopListening().
 926
 927    Use the ``verify`` argument to verify any bytes received across the wire
 928    from a client. If specified, it should be a callable which receives a
 929    single argument - the bytes of configuration data received across the
 930    network - and it should return either ``None``, to indicate that the
 931    passed in bytes could not be verified and should be discarded, or a
 932    byte string which is then passed to the configuration machinery as
 933    normal. Note that you can return transformed bytes, e.g. by decrypting
 934    the bytes passed in.
 935    """
 936
 937    class ConfigStreamHandler(StreamRequestHandler):
 938        """
 939        Handler for a logging configuration request.
 940
 941        It expects a completely new logging configuration and uses fileConfig
 942        to install it.
 943        """
 944        def handle(self):
 945            """
 946            Handle a request.
 947
 948            Each request is expected to be a 4-byte length, packed using
 949            struct.pack(">L", n), followed by the config file.
 950            Uses fileConfig() to do the grunt work.
 951            """
 952            try:
 953                conn = self.connection
 954                chunk = conn.recv(4)
 955                if len(chunk) == 4:
 956                    slen = struct.unpack(">L", chunk)[0]
 957                    chunk = self.connection.recv(slen)
 958                    while len(chunk) < slen:
 959                        chunk = chunk + conn.recv(slen - len(chunk))
 960                    if self.server.verify is not None:
 961                        chunk = self.server.verify(chunk)
 962                    if chunk is not None:   # verified, can process
 963                        chunk = chunk.decode("utf-8")
 964                        try:
 965                            import json
 966                            d =json.loads(chunk)
 967                            assert isinstance(d, dict)
 968                            dictConfig(d)
 969                        except Exception:
 970                            #Apply new configuration.
 971
 972                            file = io.StringIO(chunk)
 973                            try:
 974                                fileConfig(file)
 975                            except Exception:
 976                                traceback.print_exc()
 977                    if self.server.ready:
 978                        self.server.ready.set()
 979            except OSError as e:
 980                if e.errno != RESET_ERROR:
 981                    raise
 982
 983    class ConfigSocketReceiver(ThreadingTCPServer):
 984        """
 985        A simple TCP socket-based logging config receiver.
 986        """
 987
 988        allow_reuse_address = 1
 989
 990        def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
 991                     handler=None, ready=None, verify=None):
 992            ThreadingTCPServer.__init__(self, (host, port), handler)
 993            logging._acquireLock()
 994            self.abort = 0
 995            logging._releaseLock()
 996            self.timeout = 1
 997            self.ready = ready
 998            self.verify = verify
 999
1000        def serve_until_stopped(self):
1001            import select
1002            abort = 0
1003            while not abort:
1004                rd, wr, ex = select.select([self.socket.fileno()],
1005                                           [], [],
1006                                           self.timeout)
1007                if rd:
1008                    self.handle_request()
1009                logging._acquireLock()
1010                abort = self.abort
1011                logging._releaseLock()
1012            self.server_close()
1013
1014    class Server(threading.Thread):
1015
1016        def __init__(self, rcvr, hdlr, port, verify):
1017            super(Server, self).__init__()
1018            self.rcvr = rcvr
1019            self.hdlr = hdlr
1020            self.port = port
1021            self.verify = verify
1022            self.ready = threading.Event()
1023
1024        def run(self):
1025            server = self.rcvr(port=self.port, handler=self.hdlr,
1026                               ready=self.ready,
1027                               verify=self.verify)
1028            if self.port == 0:
1029                self.port = server.server_address[1]
1030            self.ready.set()
1031            global _listener
1032            logging._acquireLock()
1033            _listener = server
1034            logging._releaseLock()
1035            server.serve_until_stopped()
1036
1037    return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify)
1038
1039def stopListening():
1040    """
1041    Stop the listening server which was created with a call to listen().
1042    """
1043    global _listener
1044    logging._acquireLock()
1045    try:
1046        if _listener:
1047            _listener.abort = 1
1048            _listener = None
1049    finally:
1050        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):
504class DictConfigurator(BaseConfigurator):
505    """
506    Configure logging using a dictionary-like object to describe the
507    configuration.
508    """
509
510    def configure(self):
511        """Do the configuration."""
512
513        config = self.config
514        if 'version' not in config:
515            raise ValueError("dictionary doesn't specify a version")
516        if config['version'] != 1:
517            raise ValueError("Unsupported version: %s" % config['version'])
518        incremental = config.pop('incremental', False)
519        EMPTY_DICT = {}
520        logging._acquireLock()
521        try:
522            if incremental:
523                handlers = config.get('handlers', EMPTY_DICT)
524                for name in handlers:
525                    if name not in logging._handlers:
526                        raise ValueError('No handler found with '
527                                         'name %r'  % name)
528                    else:
529                        try:
530                            handler = logging._handlers[name]
531                            handler_config = handlers[name]
532                            level = handler_config.get('level', None)
533                            if level:
534                                handler.setLevel(logging._checkLevel(level))
535                        except Exception as e:
536                            raise ValueError('Unable to configure handler '
537                                             '%r' % name) from e
538                loggers = config.get('loggers', EMPTY_DICT)
539                for name in loggers:
540                    try:
541                        self.configure_logger(name, loggers[name], True)
542                    except Exception as e:
543                        raise ValueError('Unable to configure logger '
544                                         '%r' % name) from e
545                root = config.get('root', None)
546                if root:
547                    try:
548                        self.configure_root(root, True)
549                    except Exception as e:
550                        raise ValueError('Unable to configure root '
551                                         'logger') from e
552            else:
553                disable_existing = config.pop('disable_existing_loggers', True)
554
555                _clearExistingHandlers()
556
557                # Do formatters first - they don't refer to anything else
558                formatters = config.get('formatters', EMPTY_DICT)
559                for name in formatters:
560                    try:
561                        formatters[name] = self.configure_formatter(
562                                                            formatters[name])
563                    except Exception as e:
564                        raise ValueError('Unable to configure '
565                                         'formatter %r' % name) from e
566                # Next, do filters - they don't refer to anything else, either
567                filters = config.get('filters', EMPTY_DICT)
568                for name in filters:
569                    try:
570                        filters[name] = self.configure_filter(filters[name])
571                    except Exception as e:
572                        raise ValueError('Unable to configure '
573                                         'filter %r' % name) from e
574
575                # Next, do handlers - they refer to formatters and filters
576                # As handlers can refer to other handlers, sort the keys
577                # to allow a deterministic order of configuration
578                handlers = config.get('handlers', EMPTY_DICT)
579                deferred = []
580                for name in sorted(handlers):
581                    try:
582                        handler = self.configure_handler(handlers[name])
583                        handler.name = name
584                        handlers[name] = handler
585                    except Exception as e:
586                        if ' not configured yet' in str(e.__cause__):
587                            deferred.append(name)
588                        else:
589                            raise ValueError('Unable to configure handler '
590                                             '%r' % name) from e
591
592                # Now do any that were deferred
593                for name in deferred:
594                    try:
595                        handler = self.configure_handler(handlers[name])
596                        handler.name = name
597                        handlers[name] = handler
598                    except Exception as e:
599                        raise ValueError('Unable to configure handler '
600                                         '%r' % name) from e
601
602                # Next, do loggers - they refer to handlers and filters
603
604                #we don't want to lose the existing loggers,
605                #since other threads may have pointers to them.
606                #existing is set to contain all existing loggers,
607                #and as we go through the new configuration we
608                #remove any which are configured. At the end,
609                #what's left in existing is the set of loggers
610                #which were in the previous configuration but
611                #which are not in the new configuration.
612                root = logging.root
613                existing = list(root.manager.loggerDict.keys())
614                #The list needs to be sorted so that we can
615                #avoid disabling child loggers of explicitly
616                #named loggers. With a sorted list it is easier
617                #to find the child loggers.
618                existing.sort()
619                #We'll keep the list of existing loggers
620                #which are children of named loggers here...
621                child_loggers = []
622                #now set up the new ones...
623                loggers = config.get('loggers', EMPTY_DICT)
624                for name in loggers:
625                    if name in existing:
626                        i = existing.index(name) + 1 # look after name
627                        prefixed = name + "."
628                        pflen = len(prefixed)
629                        num_existing = len(existing)
630                        while i < num_existing:
631                            if existing[i][:pflen] == prefixed:
632                                child_loggers.append(existing[i])
633                            i += 1
634                        existing.remove(name)
635                    try:
636                        self.configure_logger(name, loggers[name])
637                    except Exception as e:
638                        raise ValueError('Unable to configure logger '
639                                         '%r' % name) from e
640
641                #Disable any old loggers. There's no point deleting
642                #them as other threads may continue to hold references
643                #and by disabling them, you stop them doing any logging.
644                #However, don't disable children of named loggers, as that's
645                #probably not what was intended by the user.
646                #for log in existing:
647                #    logger = root.manager.loggerDict[log]
648                #    if log in child_loggers:
649                #        logger.level = logging.NOTSET
650                #        logger.handlers = []
651                #        logger.propagate = True
652                #    elif disable_existing:
653                #        logger.disabled = True
654                _handle_existing_loggers(existing, child_loggers,
655                                         disable_existing)
656
657                # And finally, do the root logger
658                root = config.get('root', None)
659                if root:
660                    try:
661                        self.configure_root(root)
662                    except Exception as e:
663                        raise ValueError('Unable to configure root '
664                                         'logger') from e
665        finally:
666            logging._releaseLock()
667
668    def configure_formatter(self, config):
669        """Configure a formatter from a dictionary."""
670        if '()' in config:
671            factory = config['()'] # for use in exception handler
672            try:
673                result = self.configure_custom(config)
674            except TypeError as te:
675                if "'format'" not in str(te):
676                    raise
677                #Name of parameter changed from fmt to format.
678                #Retry with old name.
679                #This is so that code can be used with older Python versions
680                #(e.g. by Django)
681                config['fmt'] = config.pop('format')
682                config['()'] = factory
683                result = self.configure_custom(config)
684        else:
685            fmt = config.get('format', None)
686            dfmt = config.get('datefmt', None)
687            style = config.get('style', '%')
688            cname = config.get('class', None)
689            defaults = config.get('defaults', None)
690
691            if not cname:
692                c = logging.Formatter
693            else:
694                c = _resolve(cname)
695
696            kwargs  = {}
697
698            # Add defaults only if it exists.
699            # Prevents TypeError in custom formatter callables that do not
700            # accept it.
701            if defaults is not None:
702                kwargs['defaults'] = defaults
703
704            # A TypeError would be raised if "validate" key is passed in with a formatter callable
705            # that does not accept "validate" as a parameter
706            if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
707                result = c(fmt, dfmt, style, config['validate'], **kwargs)
708            else:
709                result = c(fmt, dfmt, style, **kwargs)
710
711        return result
712
713    def configure_filter(self, config):
714        """Configure a filter from a dictionary."""
715        if '()' in config:
716            result = self.configure_custom(config)
717        else:
718            name = config.get('name', '')
719            result = logging.Filter(name)
720        return result
721
722    def add_filters(self, filterer, filters):
723        """Add filters to a filterer from a list of names."""
724        for f in filters:
725            try:
726                if callable(f) or callable(getattr(f, 'filter', None)):
727                    filter_ = f
728                else:
729                    filter_ = self.config['filters'][f]
730                filterer.addFilter(filter_)
731            except Exception as e:
732                raise ValueError('Unable to add filter %r' % f) from e
733
734    def _configure_queue_handler(self, klass, **kwargs):
735        if 'queue' in kwargs:
736            q = kwargs['queue']
737        else:
738            q = queue.Queue()  # unbounded
739        rhl = kwargs.get('respect_handler_level', False)
740        if 'listener' in kwargs:
741            lklass = kwargs['listener']
742        else:
743            lklass = logging.handlers.QueueListener
744        listener = lklass(q, *kwargs.get('handlers', []), respect_handler_level=rhl)
745        handler = klass(q)
746        handler.listener = listener
747        return handler
748
749    def configure_handler(self, config):
750        """Configure a handler from a dictionary."""
751        config_copy = dict(config)  # for restoring in case of error
752        formatter = config.pop('formatter', None)
753        if formatter:
754            try:
755                formatter = self.config['formatters'][formatter]
756            except Exception as e:
757                raise ValueError('Unable to set formatter '
758                                 '%r' % formatter) from e
759        level = config.pop('level', None)
760        filters = config.pop('filters', None)
761        if '()' in config:
762            c = config.pop('()')
763            if not callable(c):
764                c = self.resolve(c)
765            factory = c
766        else:
767            cname = config.pop('class')
768            if callable(cname):
769                klass = cname
770            else:
771                klass = self.resolve(cname)
772            if issubclass(klass, logging.handlers.MemoryHandler) and\
773                'target' in config:
774                # Special case for handler which refers to another handler
775                try:
776                    tn = config['target']
777                    th = self.config['handlers'][tn]
778                    if not isinstance(th, logging.Handler):
779                        config.update(config_copy)  # restore for deferred cfg
780                        raise TypeError('target not configured yet')
781                    config['target'] = th
782                except Exception as e:
783                    raise ValueError('Unable to set target handler %r' % tn) from e
784            elif issubclass(klass, logging.handlers.QueueHandler):
785                # Another special case for handler which refers to other handlers
786                # if 'handlers' not in config:
787                    # raise ValueError('No handlers specified for a QueueHandler')
788                if 'queue' in config:
789                    from multiprocessing.queues import Queue as MPQueue
790                    qspec = config['queue']
791                    if not isinstance(qspec, (queue.Queue, MPQueue)):
792                        if isinstance(qspec, str):
793                            q = self.resolve(qspec)
794                            if not callable(q):
795                                raise TypeError('Invalid queue specifier %r' % qspec)
796                            q = q()
797                        elif isinstance(qspec, dict):
798                            if '()' not in qspec:
799                                raise TypeError('Invalid queue specifier %r' % qspec)
800                            q = self.configure_custom(dict(qspec))
801                        else:
802                            raise TypeError('Invalid queue specifier %r' % qspec)
803                        config['queue'] = q
804                if 'listener' in config:
805                    lspec = config['listener']
806                    if isinstance(lspec, type):
807                        if not issubclass(lspec, logging.handlers.QueueListener):
808                            raise TypeError('Invalid listener specifier %r' % lspec)
809                    else:
810                        if isinstance(lspec, str):
811                            listener = self.resolve(lspec)
812                            if isinstance(listener, type) and\
813                                not issubclass(listener, logging.handlers.QueueListener):
814                                raise TypeError('Invalid listener specifier %r' % lspec)
815                        elif isinstance(lspec, dict):
816                            if '()' not in lspec:
817                                raise TypeError('Invalid listener specifier %r' % lspec)
818                            listener = self.configure_custom(dict(lspec))
819                        else:
820                            raise TypeError('Invalid listener specifier %r' % lspec)
821                        if not callable(listener):
822                            raise TypeError('Invalid listener specifier %r' % lspec)
823                        config['listener'] = listener
824                if 'handlers' in config:
825                    hlist = []
826                    try:
827                        for hn in config['handlers']:
828                            h = self.config['handlers'][hn]
829                            if not isinstance(h, logging.Handler):
830                                config.update(config_copy)  # restore for deferred cfg
831                                raise TypeError('Required handler %r '
832                                                'is not configured yet' % hn)
833                            hlist.append(h)
834                    except Exception as e:
835                        raise ValueError('Unable to set required handler %r' % hn) from e
836                    config['handlers'] = hlist
837            elif issubclass(klass, logging.handlers.SMTPHandler) and\
838                'mailhost' in config:
839                config['mailhost'] = self.as_tuple(config['mailhost'])
840            elif issubclass(klass, logging.handlers.SysLogHandler) and\
841                'address' in config:
842                config['address'] = self.as_tuple(config['address'])
843            if issubclass(klass, logging.handlers.QueueHandler):
844                factory = functools.partial(self._configure_queue_handler, klass)
845            else:
846                factory = klass
847        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
848        try:
849            result = factory(**kwargs)
850        except TypeError as te:
851            if "'stream'" not in str(te):
852                raise
853            #The argument name changed from strm to stream
854            #Retry with old name.
855            #This is so that code can be used with older Python versions
856            #(e.g. by Django)
857            kwargs['strm'] = kwargs.pop('stream')
858            result = factory(**kwargs)
859        if formatter:
860            result.setFormatter(formatter)
861        if level is not None:
862            result.setLevel(logging._checkLevel(level))
863        if filters:
864            self.add_filters(result, filters)
865        props = config.pop('.', None)
866        if props:
867            for name, value in props.items():
868                setattr(result, name, value)
869        return result
870
871    def add_handlers(self, logger, handlers):
872        """Add handlers to a logger from a list of names."""
873        for h in handlers:
874            try:
875                logger.addHandler(self.config['handlers'][h])
876            except Exception as e:
877                raise ValueError('Unable to add handler %r' % h) from e
878
879    def common_logger_config(self, logger, config, incremental=False):
880        """
881        Perform configuration which is common to root and non-root loggers.
882        """
883        level = config.get('level', None)
884        if level is not None:
885            logger.setLevel(logging._checkLevel(level))
886        if not incremental:
887            #Remove any existing handlers
888            for h in logger.handlers[:]:
889                logger.removeHandler(h)
890            handlers = config.get('handlers', None)
891            if handlers:
892                self.add_handlers(logger, handlers)
893            filters = config.get('filters', None)
894            if filters:
895                self.add_filters(logger, filters)
896
897    def configure_logger(self, name, config, incremental=False):
898        """Configure a non-root logger from a dictionary."""
899        logger = logging.getLogger(name)
900        self.common_logger_config(logger, config, incremental)
901        logger.disabled = False
902        propagate = config.get('propagate', None)
903        if propagate is not None:
904            logger.propagate = propagate
905
906    def configure_root(self, config, incremental=False):
907        """Configure a root logger from a dictionary."""
908        root = logging.getLogger()
909        self.common_logger_config(root, config, incremental)

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

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

Do the configuration.

def configure_formatter(self, config):
668    def configure_formatter(self, config):
669        """Configure a formatter from a dictionary."""
670        if '()' in config:
671            factory = config['()'] # for use in exception handler
672            try:
673                result = self.configure_custom(config)
674            except TypeError as te:
675                if "'format'" not in str(te):
676                    raise
677                #Name of parameter changed from fmt to format.
678                #Retry with old name.
679                #This is so that code can be used with older Python versions
680                #(e.g. by Django)
681                config['fmt'] = config.pop('format')
682                config['()'] = factory
683                result = self.configure_custom(config)
684        else:
685            fmt = config.get('format', None)
686            dfmt = config.get('datefmt', None)
687            style = config.get('style', '%')
688            cname = config.get('class', None)
689            defaults = config.get('defaults', None)
690
691            if not cname:
692                c = logging.Formatter
693            else:
694                c = _resolve(cname)
695
696            kwargs  = {}
697
698            # Add defaults only if it exists.
699            # Prevents TypeError in custom formatter callables that do not
700            # accept it.
701            if defaults is not None:
702                kwargs['defaults'] = defaults
703
704            # A TypeError would be raised if "validate" key is passed in with a formatter callable
705            # that does not accept "validate" as a parameter
706            if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
707                result = c(fmt, dfmt, style, config['validate'], **kwargs)
708            else:
709                result = c(fmt, dfmt, style, **kwargs)
710
711        return result

Configure a formatter from a dictionary.

def configure_filter(self, config):
713    def configure_filter(self, config):
714        """Configure a filter from a dictionary."""
715        if '()' in config:
716            result = self.configure_custom(config)
717        else:
718            name = config.get('name', '')
719            result = logging.Filter(name)
720        return result

Configure a filter from a dictionary.

def add_filters(self, filterer, filters):
722    def add_filters(self, filterer, filters):
723        """Add filters to a filterer from a list of names."""
724        for f in filters:
725            try:
726                if callable(f) or callable(getattr(f, 'filter', None)):
727                    filter_ = f
728                else:
729                    filter_ = self.config['filters'][f]
730                filterer.addFilter(filter_)
731            except Exception as e:
732                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):
749    def configure_handler(self, config):
750        """Configure a handler from a dictionary."""
751        config_copy = dict(config)  # for restoring in case of error
752        formatter = config.pop('formatter', None)
753        if formatter:
754            try:
755                formatter = self.config['formatters'][formatter]
756            except Exception as e:
757                raise ValueError('Unable to set formatter '
758                                 '%r' % formatter) from e
759        level = config.pop('level', None)
760        filters = config.pop('filters', None)
761        if '()' in config:
762            c = config.pop('()')
763            if not callable(c):
764                c = self.resolve(c)
765            factory = c
766        else:
767            cname = config.pop('class')
768            if callable(cname):
769                klass = cname
770            else:
771                klass = self.resolve(cname)
772            if issubclass(klass, logging.handlers.MemoryHandler) and\
773                'target' in config:
774                # Special case for handler which refers to another handler
775                try:
776                    tn = config['target']
777                    th = self.config['handlers'][tn]
778                    if not isinstance(th, logging.Handler):
779                        config.update(config_copy)  # restore for deferred cfg
780                        raise TypeError('target not configured yet')
781                    config['target'] = th
782                except Exception as e:
783                    raise ValueError('Unable to set target handler %r' % tn) from e
784            elif issubclass(klass, logging.handlers.QueueHandler):
785                # Another special case for handler which refers to other handlers
786                # if 'handlers' not in config:
787                    # raise ValueError('No handlers specified for a QueueHandler')
788                if 'queue' in config:
789                    from multiprocessing.queues import Queue as MPQueue
790                    qspec = config['queue']
791                    if not isinstance(qspec, (queue.Queue, MPQueue)):
792                        if isinstance(qspec, str):
793                            q = self.resolve(qspec)
794                            if not callable(q):
795                                raise TypeError('Invalid queue specifier %r' % qspec)
796                            q = q()
797                        elif isinstance(qspec, dict):
798                            if '()' not in qspec:
799                                raise TypeError('Invalid queue specifier %r' % qspec)
800                            q = self.configure_custom(dict(qspec))
801                        else:
802                            raise TypeError('Invalid queue specifier %r' % qspec)
803                        config['queue'] = q
804                if 'listener' in config:
805                    lspec = config['listener']
806                    if isinstance(lspec, type):
807                        if not issubclass(lspec, logging.handlers.QueueListener):
808                            raise TypeError('Invalid listener specifier %r' % lspec)
809                    else:
810                        if isinstance(lspec, str):
811                            listener = self.resolve(lspec)
812                            if isinstance(listener, type) and\
813                                not issubclass(listener, logging.handlers.QueueListener):
814                                raise TypeError('Invalid listener specifier %r' % lspec)
815                        elif isinstance(lspec, dict):
816                            if '()' not in lspec:
817                                raise TypeError('Invalid listener specifier %r' % lspec)
818                            listener = self.configure_custom(dict(lspec))
819                        else:
820                            raise TypeError('Invalid listener specifier %r' % lspec)
821                        if not callable(listener):
822                            raise TypeError('Invalid listener specifier %r' % lspec)
823                        config['listener'] = listener
824                if 'handlers' in config:
825                    hlist = []
826                    try:
827                        for hn in config['handlers']:
828                            h = self.config['handlers'][hn]
829                            if not isinstance(h, logging.Handler):
830                                config.update(config_copy)  # restore for deferred cfg
831                                raise TypeError('Required handler %r '
832                                                'is not configured yet' % hn)
833                            hlist.append(h)
834                    except Exception as e:
835                        raise ValueError('Unable to set required handler %r' % hn) from e
836                    config['handlers'] = hlist
837            elif issubclass(klass, logging.handlers.SMTPHandler) and\
838                'mailhost' in config:
839                config['mailhost'] = self.as_tuple(config['mailhost'])
840            elif issubclass(klass, logging.handlers.SysLogHandler) and\
841                'address' in config:
842                config['address'] = self.as_tuple(config['address'])
843            if issubclass(klass, logging.handlers.QueueHandler):
844                factory = functools.partial(self._configure_queue_handler, klass)
845            else:
846                factory = klass
847        kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
848        try:
849            result = factory(**kwargs)
850        except TypeError as te:
851            if "'stream'" not in str(te):
852                raise
853            #The argument name changed from strm to stream
854            #Retry with old name.
855            #This is so that code can be used with older Python versions
856            #(e.g. by Django)
857            kwargs['strm'] = kwargs.pop('stream')
858            result = factory(**kwargs)
859        if formatter:
860            result.setFormatter(formatter)
861        if level is not None:
862            result.setLevel(logging._checkLevel(level))
863        if filters:
864            self.add_filters(result, filters)
865        props = config.pop('.', None)
866        if props:
867            for name, value in props.items():
868                setattr(result, name, value)
869        return result

Configure a handler from a dictionary.

def add_handlers(self, logger, handlers):
871    def add_handlers(self, logger, handlers):
872        """Add handlers to a logger from a list of names."""
873        for h in handlers:
874            try:
875                logger.addHandler(self.config['handlers'][h])
876            except Exception as e:
877                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):
879    def common_logger_config(self, logger, config, incremental=False):
880        """
881        Perform configuration which is common to root and non-root loggers.
882        """
883        level = config.get('level', None)
884        if level is not None:
885            logger.setLevel(logging._checkLevel(level))
886        if not incremental:
887            #Remove any existing handlers
888            for h in logger.handlers[:]:
889                logger.removeHandler(h)
890            handlers = config.get('handlers', None)
891            if handlers:
892                self.add_handlers(logger, handlers)
893            filters = config.get('filters', None)
894            if filters:
895                self.add_filters(logger, filters)

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

def configure_logger(self, name, config, incremental=False):
897    def configure_logger(self, name, config, incremental=False):
898        """Configure a non-root logger from a dictionary."""
899        logger = logging.getLogger(name)
900        self.common_logger_config(logger, config, incremental)
901        logger.disabled = False
902        propagate = config.get('propagate', None)
903        if propagate is not None:
904            logger.propagate = propagate

Configure a non-root logger from a dictionary.

def configure_root(self, config, incremental=False):
906    def configure_root(self, config, incremental=False):
907        """Configure a root logger from a dictionary."""
908        root = logging.getLogger()
909        self.common_logger_config(root, config, incremental)

Configure a root logger from a dictionary.

dictConfigClass = <class 'DictConfigurator'>
def dictConfig(config):
913def dictConfig(config):
914    """Configure logging using a dictionary."""
915    dictConfigClass(config).configure()

Configure logging using a dictionary.

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

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