annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/logging/config.py @ 68:5028fdace37b

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