annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/logging/config.py @ 69:33d812a61356

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