python (3.11.7)

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