1  # Copyright 2001-2022 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  """
      18  Configuration functions for the logging package for Python. The core package
      19  is based on PEP 282 and comments thereto in comp.lang.python, and influenced
      20  by Apache's log4j system.
      21  
      22  Copyright (C) 2001-2022 Vinay Sajip. All Rights Reserved.
      23  
      24  To use, simply 'import logging' and log away!
      25  """
      26  
      27  import errno
      28  import functools
      29  import io
      30  import logging
      31  import logging.handlers
      32  import os
      33  import queue
      34  import re
      35  import struct
      36  import threading
      37  import traceback
      38  
      39  from socketserver import ThreadingTCPServer, StreamRequestHandler
      40  
      41  
      42  DEFAULT_LOGGING_CONFIG_PORT = 9030
      43  
      44  RESET_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  
      53  def 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  
      97  def _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  
     111  def _strip_spaces(alist):
     112      return map(str.strip, alist)
     113  
     114  def _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  
     143  def _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  
     181  def _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  
     203  def _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  
     290  def _clearExistingHandlers():
     291      """Clear and close existing handlers"""
     292      logging._handlers.clear()
     293      logging.shutdown(logging._handlerList[:])
     294      del logging._handlerList[:]
     295  
     296  
     297  IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
     298  
     299  
     300  def 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  
     307  class ESC[4;38;5;81mConvertingMixin(ESC[4;38;5;149mobject):
     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  
     340  class ESC[4;38;5;81mConvertingDict(ESC[4;38;5;149mdict, ESC[4;38;5;149mConvertingMixin):
     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  
     355  class ESC[4;38;5;81mConvertingList(ESC[4;38;5;149mlist, ESC[4;38;5;149mConvertingMixin):
     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  
     365  class ESC[4;38;5;81mConvertingTuple(ESC[4;38;5;149mtuple, ESC[4;38;5;149mConvertingMixin):
     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  
     372  class ESC[4;38;5;81mBaseConfigurator(ESC[4;38;5;149mobject):
     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          props = config.pop('.', None)
     489          # Check for valid identifiers
     490          kwargs = {k: config[k] for k in config if valid_ident(k)}
     491          result = c(**kwargs)
     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  
     503  class ESC[4;38;5;81mDictConfigurator(ESC[4;38;5;149mBaseConfigurator):
     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['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                      qspec = config['queue']
     789                      if not isinstance(qspec, queue.Queue):
     790                          if isinstance(qspec, str):
     791                              q = self.resolve(qspec)
     792                              if not callable(q):
     793                                  raise TypeError('Invalid queue specifier %r' % qspec)
     794                              q = q()
     795                          elif isinstance(qspec, dict):
     796                              if '()' not in qspec:
     797                                  raise TypeError('Invalid queue specifier %r' % qspec)
     798                              q = self.configure_custom(dict(qspec))
     799                          else:
     800                              raise TypeError('Invalid queue specifier %r' % qspec)
     801                          config['queue'] = q
     802                  if 'listener' in config:
     803                      lspec = config['listener']
     804                      if isinstance(lspec, type):
     805                          if not issubclass(lspec, logging.handlers.QueueListener):
     806                              raise TypeError('Invalid listener specifier %r' % lspec)
     807                      else:
     808                          if isinstance(lspec, str):
     809                              listener = self.resolve(lspec)
     810                              if isinstance(listener, type) and\
     811                                  not issubclass(listener, logging.handlers.QueueListener):
     812                                  raise TypeError('Invalid listener specifier %r' % lspec)
     813                          elif isinstance(lspec, dict):
     814                              if '()' not in lspec:
     815                                  raise TypeError('Invalid listener specifier %r' % lspec)
     816                              listener = self.configure_custom(dict(lspec))
     817                          else:
     818                              raise TypeError('Invalid listener specifier %r' % lspec)
     819                          if not callable(listener):
     820                              raise TypeError('Invalid listener specifier %r' % lspec)
     821                          config['listener'] = listener
     822                  hlist = []
     823                  try:
     824                      for hn in config['handlers']:
     825                          h = self.config['handlers'][hn]
     826                          if not isinstance(h, logging.Handler):
     827                              config.update(config_copy)  # restore for deferred cfg
     828                              raise TypeError('Required handler %r '
     829                                              'is not configured yet' % hn)
     830                          hlist.append(h)
     831                  except Exception as e:
     832                      raise ValueError('Unable to set required handler %r' % hn) from e
     833                  config['handlers'] = hlist
     834              elif issubclass(klass, logging.handlers.SMTPHandler) and\
     835                  'mailhost' in config:
     836                  config['mailhost'] = self.as_tuple(config['mailhost'])
     837              elif issubclass(klass, logging.handlers.SysLogHandler) and\
     838                  'address' in config:
     839                  config['address'] = self.as_tuple(config['address'])
     840              if issubclass(klass, logging.handlers.QueueHandler):
     841                  factory = functools.partial(self._configure_queue_handler, klass)
     842              else:
     843                  factory = klass
     844          props = config.pop('.', None)
     845          kwargs = {k: config[k] for k in config if valid_ident(k)}
     846          try:
     847              result = factory(**kwargs)
     848          except TypeError as te:
     849              if "'stream'" not in str(te):
     850                  raise
     851              #The argument name changed from strm to stream
     852              #Retry with old name.
     853              #This is so that code can be used with older Python versions
     854              #(e.g. by Django)
     855              kwargs['strm'] = kwargs.pop('stream')
     856              result = factory(**kwargs)
     857          if formatter:
     858              result.setFormatter(formatter)
     859          if level is not None:
     860              result.setLevel(logging._checkLevel(level))
     861          if filters:
     862              self.add_filters(result, filters)
     863          if props:
     864              for name, value in props.items():
     865                  setattr(result, name, value)
     866          return result
     867  
     868      def add_handlers(self, logger, handlers):
     869          """Add handlers to a logger from a list of names."""
     870          for h in handlers:
     871              try:
     872                  logger.addHandler(self.config['handlers'][h])
     873              except Exception as e:
     874                  raise ValueError('Unable to add handler %r' % h) from e
     875  
     876      def common_logger_config(self, logger, config, incremental=False):
     877          """
     878          Perform configuration which is common to root and non-root loggers.
     879          """
     880          level = config.get('level', None)
     881          if level is not None:
     882              logger.setLevel(logging._checkLevel(level))
     883          if not incremental:
     884              #Remove any existing handlers
     885              for h in logger.handlers[:]:
     886                  logger.removeHandler(h)
     887              handlers = config.get('handlers', None)
     888              if handlers:
     889                  self.add_handlers(logger, handlers)
     890              filters = config.get('filters', None)
     891              if filters:
     892                  self.add_filters(logger, filters)
     893  
     894      def configure_logger(self, name, config, incremental=False):
     895          """Configure a non-root logger from a dictionary."""
     896          logger = logging.getLogger(name)
     897          self.common_logger_config(logger, config, incremental)
     898          logger.disabled = False
     899          propagate = config.get('propagate', None)
     900          if propagate is not None:
     901              logger.propagate = propagate
     902  
     903      def configure_root(self, config, incremental=False):
     904          """Configure a root logger from a dictionary."""
     905          root = logging.getLogger()
     906          self.common_logger_config(root, config, incremental)
     907  
     908  dictConfigClass = DictConfigurator
     909  
     910  def dictConfig(config):
     911      """Configure logging using a dictionary."""
     912      dictConfigClass(config).configure()
     913  
     914  
     915  def listen(port=DEFAULT_LOGGING_CONFIG_PORT, verify=None):
     916      """
     917      Start up a socket server on the specified port, and listen for new
     918      configurations.
     919  
     920      These will be sent as a file suitable for processing by fileConfig().
     921      Returns a Thread object on which you can call start() to start the server,
     922      and which you can join() when appropriate. To stop the server, call
     923      stopListening().
     924  
     925      Use the ``verify`` argument to verify any bytes received across the wire
     926      from a client. If specified, it should be a callable which receives a
     927      single argument - the bytes of configuration data received across the
     928      network - and it should return either ``None``, to indicate that the
     929      passed in bytes could not be verified and should be discarded, or a
     930      byte string which is then passed to the configuration machinery as
     931      normal. Note that you can return transformed bytes, e.g. by decrypting
     932      the bytes passed in.
     933      """
     934  
     935      class ESC[4;38;5;81mConfigStreamHandler(ESC[4;38;5;149mStreamRequestHandler):
     936          """
     937          Handler for a logging configuration request.
     938  
     939          It expects a completely new logging configuration and uses fileConfig
     940          to install it.
     941          """
     942          def handle(self):
     943              """
     944              Handle a request.
     945  
     946              Each request is expected to be a 4-byte length, packed using
     947              struct.pack(">L", n), followed by the config file.
     948              Uses fileConfig() to do the grunt work.
     949              """
     950              try:
     951                  conn = self.connection
     952                  chunk = conn.recv(4)
     953                  if len(chunk) == 4:
     954                      slen = struct.unpack(">L", chunk)[0]
     955                      chunk = self.connection.recv(slen)
     956                      while len(chunk) < slen:
     957                          chunk = chunk + conn.recv(slen - len(chunk))
     958                      if self.server.verify is not None:
     959                          chunk = self.server.verify(chunk)
     960                      if chunk is not None:   # verified, can process
     961                          chunk = chunk.decode("utf-8")
     962                          try:
     963                              import json
     964                              d =json.loads(chunk)
     965                              assert isinstance(d, dict)
     966                              dictConfig(d)
     967                          except Exception:
     968                              #Apply new configuration.
     969  
     970                              file = io.StringIO(chunk)
     971                              try:
     972                                  fileConfig(file)
     973                              except Exception:
     974                                  traceback.print_exc()
     975                      if self.server.ready:
     976                          self.server.ready.set()
     977              except OSError as e:
     978                  if e.errno != RESET_ERROR:
     979                      raise
     980  
     981      class ESC[4;38;5;81mConfigSocketReceiver(ESC[4;38;5;149mThreadingTCPServer):
     982          """
     983          A simple TCP socket-based logging config receiver.
     984          """
     985  
     986          allow_reuse_address = 1
     987  
     988          def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
     989                       handler=None, ready=None, verify=None):
     990              ThreadingTCPServer.__init__(self, (host, port), handler)
     991              logging._acquireLock()
     992              self.abort = 0
     993              logging._releaseLock()
     994              self.timeout = 1
     995              self.ready = ready
     996              self.verify = verify
     997  
     998          def serve_until_stopped(self):
     999              import select
    1000              abort = 0
    1001              while not abort:
    1002                  rd, wr, ex = select.select([self.socket.fileno()],
    1003                                             [], [],
    1004                                             self.timeout)
    1005                  if rd:
    1006                      self.handle_request()
    1007                  logging._acquireLock()
    1008                  abort = self.abort
    1009                  logging._releaseLock()
    1010              self.server_close()
    1011  
    1012      class ESC[4;38;5;81mServer(ESC[4;38;5;149mthreadingESC[4;38;5;149m.ESC[4;38;5;149mThread):
    1013  
    1014          def __init__(self, rcvr, hdlr, port, verify):
    1015              super(Server, self).__init__()
    1016              self.rcvr = rcvr
    1017              self.hdlr = hdlr
    1018              self.port = port
    1019              self.verify = verify
    1020              self.ready = threading.Event()
    1021  
    1022          def run(self):
    1023              server = self.rcvr(port=self.port, handler=self.hdlr,
    1024                                 ready=self.ready,
    1025                                 verify=self.verify)
    1026              if self.port == 0:
    1027                  self.port = server.server_address[1]
    1028              self.ready.set()
    1029              global _listener
    1030              logging._acquireLock()
    1031              _listener = server
    1032              logging._releaseLock()
    1033              server.serve_until_stopped()
    1034  
    1035      return Server(ConfigSocketReceiver, ConfigStreamHandler, port, verify)
    1036  
    1037  def stopListening():
    1038      """
    1039      Stop the listening server which was created with a call to listen().
    1040      """
    1041      global _listener
    1042      logging._acquireLock()
    1043      try:
    1044          if _listener:
    1045              _listener.abort = 1
    1046              _listener = None
    1047      finally:
    1048          logging._releaseLock()