blob: f5971c2bcfbc6da4f99a7b30fa33f8abdcc6f5f0 [file] [log] [blame]
Vinay Sajipdb81c4c2010-02-25 23:13:06 +00001# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +00002#
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.
Guido van Rossum57102f82002-11-13 16:15:58 +000016
17"""
Vinay Sajip3f742842004-02-28 16:07:46 +000018Configuration functions for the logging package for Python. The core package
19is based on PEP 282 and comments thereto in comp.lang.python, and influenced
20by Apache's log4j system.
Guido van Rossum57102f82002-11-13 16:15:58 +000021
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000022Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000023
24To use, simply 'import logging' and log away!
25"""
26
Vinay Sajipdb81c4c2010-02-25 23:13:06 +000027import sys, logging, logging.handlers, socket, struct, os, traceback, re
28import types, io
Vinay Sajip612df8e2005-02-18 11:54:46 +000029
30try:
Georg Brandl2067bfd2008-05-25 13:05:15 +000031 import _thread as thread
Vinay Sajip612df8e2005-02-18 11:54:46 +000032 import threading
33except ImportError:
34 thread = None
Guido van Rossum57102f82002-11-13 16:15:58 +000035
Alexandre Vassalottice261952008-05-12 02:31:37 +000036from socketserver import ThreadingTCPServer, StreamRequestHandler
Guido van Rossum57102f82002-11-13 16:15:58 +000037
38
39DEFAULT_LOGGING_CONFIG_PORT = 9030
40
Vinay Sajip326441e2004-02-20 13:16:36 +000041if sys.platform == "win32":
42 RESET_ERROR = 10054 #WSAECONNRESET
43else:
44 RESET_ERROR = 104 #ECONNRESET
45
Guido van Rossum57102f82002-11-13 16:15:58 +000046#
47# The following code implements a socket listener for on-the-fly
48# reconfiguration of logging.
49#
50# _listener holds the server object doing the listening
51_listener = None
52
Georg Brandl472f2e22009-06-08 08:58:54 +000053def fileConfig(fname, defaults=None, disable_existing_loggers=True):
Guido van Rossum57102f82002-11-13 16:15:58 +000054 """
55 Read the logging configuration from a ConfigParser-format file.
56
57 This can be called several times from an application, allowing an end user
58 the ability to select from various pre-canned configurations (if the
59 developer provides a mechanism to present the choices and load the chosen
60 configuration).
Guido van Rossum57102f82002-11-13 16:15:58 +000061 """
Alexandre Vassalotti1d1eaa42008-05-14 22:59:42 +000062 import configparser
Guido van Rossum57102f82002-11-13 16:15:58 +000063
Alexandre Vassalotti1d1eaa42008-05-14 22:59:42 +000064 cp = configparser.ConfigParser(defaults)
Georg Brandl01e4d572010-02-06 22:27:51 +000065 if hasattr(fname, 'readline'):
Guido van Rossum57102f82002-11-13 16:15:58 +000066 cp.readfp(fname)
67 else:
68 cp.read(fname)
Vinay Sajip989b69a2006-01-16 21:28:37 +000069
70 formatters = _create_formatters(cp)
71
72 # critical section
Guido van Rossum57102f82002-11-13 16:15:58 +000073 logging._acquireLock()
74 try:
Vinay Sajip989b69a2006-01-16 21:28:37 +000075 logging._handlers.clear()
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000076 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000077 # Handlers add themselves to logging._handlers
78 handlers = _install_handlers(cp, formatters)
Benjamin Petersonfea6a942008-07-02 16:11:42 +000079 _install_loggers(cp, handlers, disable_existing_loggers)
Guido van Rossum57102f82002-11-13 16:15:58 +000080 finally:
81 logging._releaseLock()
82
Vinay Sajip989b69a2006-01-16 21:28:37 +000083
Vinay Sajip7a7160b2006-01-20 18:28:03 +000084def _resolve(name):
85 """Resolve a dotted name to a global object."""
Neal Norwitz9d72bb42007-04-17 08:48:32 +000086 name = name.split('.')
Vinay Sajip7a7160b2006-01-20 18:28:03 +000087 used = name.pop(0)
88 found = __import__(used)
89 for n in name:
90 used = used + '.' + n
91 try:
92 found = getattr(found, n)
93 except AttributeError:
94 __import__(used)
95 found = getattr(found, n)
96 return found
97
Benjamin Petersonae5360b2008-09-08 23:05:23 +000098def _strip_spaces(alist):
99 return map(lambda x: x.strip(), alist)
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000100
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000101def _encoded(s):
102 return s if isinstance(s, str) else s.encode('utf-8')
103
Vinay Sajip989b69a2006-01-16 21:28:37 +0000104def _create_formatters(cp):
105 """Create and return formatters"""
106 flist = cp.get("formatters", "keys")
107 if not len(flist):
108 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000109 flist = flist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000110 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000111 formatters = {}
112 for form in flist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000113 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000114 opts = cp.options(sectname)
115 if "format" in opts:
116 fs = cp.get(sectname, "format", 1)
117 else:
118 fs = None
119 if "datefmt" in opts:
120 dfs = cp.get(sectname, "datefmt", 1)
121 else:
122 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000123 c = logging.Formatter
124 if "class" in opts:
125 class_name = cp.get(sectname, "class")
126 if class_name:
127 c = _resolve(class_name)
128 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000129 formatters[form] = f
130 return formatters
131
132
133def _install_handlers(cp, formatters):
134 """Install and return handlers"""
135 hlist = cp.get("handlers", "keys")
136 if not len(hlist):
137 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000138 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000139 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000140 handlers = {}
141 fixups = [] #for inter-handler references
142 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000143 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000144 klass = cp.get(sectname, "class")
145 opts = cp.options(sectname)
146 if "formatter" in opts:
147 fmt = cp.get(sectname, "formatter")
148 else:
149 fmt = ""
Georg Brandl3dbca812008-07-23 16:10:53 +0000150 try:
151 klass = eval(klass, vars(logging))
152 except (AttributeError, NameError):
153 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000154 args = cp.get(sectname, "args")
155 args = eval(args, vars(logging))
Neal Norwitzd9108552006-03-17 08:00:19 +0000156 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000157 if "level" in opts:
158 level = cp.get(sectname, "level")
159 h.setLevel(logging._levelNames[level])
160 if len(fmt):
161 h.setFormatter(formatters[fmt])
Benjamin Peterson41181742008-07-02 20:22:54 +0000162 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000163 if "target" in opts:
164 target = cp.get(sectname,"target")
165 else:
166 target = ""
167 if len(target): #the target handler may not be loaded yet, so keep for later...
168 fixups.append((h, target))
169 handlers[hand] = h
170 #now all handlers are loaded, fixup inter-handler references...
171 for h, t in fixups:
172 h.setTarget(handlers[t])
173 return handlers
174
175
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000176def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000177 """Create and install loggers"""
178
179 # configure the root first
180 llist = cp.get("loggers", "keys")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000181 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000182 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000183 llist.remove("root")
184 sectname = "logger_root"
185 root = logging.root
186 log = root
187 opts = cp.options(sectname)
188 if "level" in opts:
189 level = cp.get(sectname, "level")
190 log.setLevel(logging._levelNames[level])
191 for h in root.handlers[:]:
192 root.removeHandler(h)
193 hlist = cp.get(sectname, "handlers")
194 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000195 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000196 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000197 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000198 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000199
200 #and now the others...
201 #we don't want to lose the existing loggers,
202 #since other threads may have pointers to them.
203 #existing is set to contain all existing loggers,
204 #and as we go through the new configuration we
205 #remove any which are configured. At the end,
206 #what's left in existing is the set of loggers
207 #which were in the previous configuration but
208 #which are not in the new configuration.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000209 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +0000210 #The list needs to be sorted so that we can
211 #avoid disabling child loggers of explicitly
212 #named loggers. With a sorted list it is easier
213 #to find the child loggers.
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000214 existing.sort(key=_encoded)
Christian Heimes96f31632007-11-12 01:32:03 +0000215 #We'll keep the list of existing loggers
216 #which are children of named loggers here...
217 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000218 #now set up the new ones...
219 for log in llist:
220 sectname = "logger_%s" % log
221 qn = cp.get(sectname, "qualname")
222 opts = cp.options(sectname)
223 if "propagate" in opts:
224 propagate = cp.getint(sectname, "propagate")
225 else:
226 propagate = 1
227 logger = logging.getLogger(qn)
228 if qn in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000229 i = existing.index(qn)
230 prefixed = qn + "."
231 pflen = len(prefixed)
232 num_existing = len(existing)
233 i = i + 1 # look at the entry after qn
234 while (i < num_existing) and (existing[i][:pflen] == prefixed):
235 child_loggers.append(existing[i])
236 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000237 existing.remove(qn)
238 if "level" in opts:
239 level = cp.get(sectname, "level")
240 logger.setLevel(logging._levelNames[level])
241 for h in logger.handlers[:]:
242 logger.removeHandler(h)
243 logger.propagate = propagate
244 logger.disabled = 0
245 hlist = cp.get(sectname, "handlers")
246 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000247 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000248 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000249 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000250 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000251
252 #Disable any old loggers. There's no point deleting
253 #them as other threads may continue to hold references
254 #and by disabling them, you stop them doing any logging.
Christian Heimes96f31632007-11-12 01:32:03 +0000255 #However, don't disable children of named loggers, as that's
256 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000257 for log in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000258 logger = root.manager.loggerDict[log]
259 if log in child_loggers:
260 logger.level = logging.NOTSET
261 logger.handlers = []
262 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000263 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000264 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000265
266
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000267IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
268
269
270def valid_ident(s):
271 m = IDENTIFIER.match(s)
272 if not m:
273 raise ValueError('Not a valid Python identifier: %r' % s)
274 return True
275
276
277# The ConvertingXXX classes are wrappers around standard Python containers,
278# and they serve to convert any suitable values in the container. The
279# conversion converts base dicts, lists and tuples to their wrapped
280# equivalents, whereas strings which match a conversion format are converted
281# appropriately.
282#
283# Each wrapper should have a configurator attribute holding the actual
284# configurator to use for conversion.
285
286class ConvertingDict(dict):
287 """A converting dictionary wrapper."""
288
289 def __getitem__(self, key):
290 value = dict.__getitem__(self, key)
291 result = self.configurator.convert(value)
292 #If the converted value is different, save for next time
293 if value is not result:
294 self[key] = result
295 if type(result) in (ConvertingDict, ConvertingList,
296 ConvertingTuple):
297 result.parent = self
298 result.key = key
299 return result
300
301 def get(self, key, default=None):
302 value = dict.get(self, key, default)
303 result = self.configurator.convert(value)
304 #If the converted value is different, save for next time
305 if value is not result:
306 self[key] = result
307 if type(result) in (ConvertingDict, ConvertingList,
308 ConvertingTuple):
309 result.parent = self
310 result.key = key
311 return result
312
313 def pop(self, key, default=None):
314 value = dict.pop(self, key, default)
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 result.key = key
321 return result
322
323class ConvertingList(list):
324 """A converting list wrapper."""
325 def __getitem__(self, key):
326 value = list.__getitem__(self, key)
327 result = self.configurator.convert(value)
328 #If the converted value is different, save for next time
329 if value is not result:
330 self[key] = result
331 if type(result) in (ConvertingDict, ConvertingList,
332 ConvertingTuple):
333 result.parent = self
334 result.key = key
335 return result
336
337 def pop(self, idx=-1):
338 value = list.pop(self, idx)
339 result = self.configurator.convert(value)
340 if value is not result:
341 if type(result) in (ConvertingDict, ConvertingList,
342 ConvertingTuple):
343 result.parent = self
344 return result
345
346class ConvertingTuple(tuple):
347 """A converting tuple wrapper."""
348 def __getitem__(self, key):
349 value = tuple.__getitem__(self, key)
350 result = self.configurator.convert(value)
351 if value is not result:
352 if type(result) in (ConvertingDict, ConvertingList,
353 ConvertingTuple):
354 result.parent = self
355 result.key = key
356 return result
357
358class BaseConfigurator(object):
359 """
360 The configurator base class which defines some useful defaults.
361 """
362
363 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
364
365 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
366 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
367 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
368 DIGIT_PATTERN = re.compile(r'^\d+$')
369
370 value_converters = {
371 'ext' : 'ext_convert',
372 'cfg' : 'cfg_convert',
373 }
374
375 # We might want to use a different one, e.g. importlib
376 importer = __import__
377
378 def __init__(self, config):
379 self.config = ConvertingDict(config)
380 self.config.configurator = self
381
382 def resolve(self, s):
383 """
384 Resolve strings to objects using standard import and attribute
385 syntax.
386 """
387 name = s.split('.')
388 used = name.pop(0)
389 found = self.importer(used)
390 for frag in name:
391 used += '.' + frag
392 try:
393 found = getattr(found, frag)
394 except AttributeError:
395 self.importer(used)
396 found = getattr(found, frag)
397 return found
398
399 def ext_convert(self, value):
400 """Default converter for the ext:// protocol."""
401 return self.resolve(value)
402
403 def cfg_convert(self, value):
404 """Default converter for the cfg:// protocol."""
405 rest = value
406 m = self.WORD_PATTERN.match(rest)
407 if m is None:
408 raise ValueError("Unable to convert %r" % value)
409 else:
410 rest = rest[m.end():]
411 d = self.config[m.groups()[0]]
412 #print d, rest
413 while rest:
414 m = self.DOT_PATTERN.match(rest)
415 if m:
416 d = d[m.groups()[0]]
417 else:
418 m = self.INDEX_PATTERN.match(rest)
419 if m:
420 idx = m.groups()[0]
421 if not self.DIGIT_PATTERN.match(idx):
422 d = d[idx]
423 else:
424 try:
425 n = int(idx) # try as number first (most likely)
426 d = d[n]
427 except TypeError:
428 d = d[idx]
429 if m:
430 rest = rest[m.end():]
431 else:
432 raise ValueError('Unable to convert '
433 '%r at %r' % (value, rest))
434 #rest should be empty
435 return d
436
437 def convert(self, value):
438 """
439 Convert values to an appropriate type. dicts, lists and tuples are
440 replaced by their converting alternatives. Strings are checked to
441 see if they have a conversion format and are converted if they do.
442 """
443 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
444 value = ConvertingDict(value)
445 value.configurator = self
446 elif not isinstance(value, ConvertingList) and isinstance(value, list):
447 value = ConvertingList(value)
448 value.configurator = self
449 elif not isinstance(value, ConvertingTuple) and\
450 isinstance(value, tuple):
451 value = ConvertingTuple(value)
452 value.configurator = self
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000453 elif isinstance(value, str): # str for py3k
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000454 m = self.CONVERT_PATTERN.match(value)
455 if m:
456 d = m.groupdict()
457 prefix = d['prefix']
458 converter = self.value_converters.get(prefix, None)
459 if converter:
460 suffix = d['suffix']
461 converter = getattr(self, converter)
462 value = converter(suffix)
463 return value
464
465 def configure_custom(self, config):
466 """Configure an object with a user-supplied factory."""
467 c = config.pop('()')
468 if not hasattr(c, '__call__'):
469 c = self.resolve(c)
470 props = config.pop('.', None)
471 # Check for valid identifiers
472 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
473 result = c(**kwargs)
474 if props:
475 for name, value in props.items():
476 setattr(result, name, value)
477 return result
478
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000479 def as_tuple(self, value):
480 """Utility function which converts lists to tuples."""
481 if isinstance(value, list):
482 value = tuple(value)
483 return value
484
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000485class DictConfigurator(BaseConfigurator):
486 """
487 Configure logging using a dictionary-like object to describe the
488 configuration.
489 """
490
491 def configure(self):
492 """Do the configuration."""
493
494 config = self.config
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000495 if 'version' not in config:
496 raise ValueError("dictionary doesn't specify a version")
497 if config['version'] != 1:
498 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000499 incremental = config.pop('incremental', False)
500 EMPTY_DICT = {}
501 logging._acquireLock()
502 try:
503 if incremental:
504 handlers = config.get('handlers', EMPTY_DICT)
505 for name in handlers:
506 if name not in logging._handlers:
507 raise ValueError('No handler found with '
508 'name %r' % name)
509 else:
510 try:
511 handler = logging._handlers[name]
512 handler_config = handlers[name]
513 level = handler_config.get('level', None)
514 if level:
515 handler.setLevel(logging._checkLevel(level))
516 except Exception as e:
517 raise ValueError('Unable to configure handler '
518 '%r: %s' % (name, e))
519 loggers = config.get('loggers', EMPTY_DICT)
520 for name in loggers:
521 try:
522 self.configure_logger(name, loggers[name], True)
523 except Exception as e:
524 raise ValueError('Unable to configure logger '
525 '%r: %s' % (name, e))
526 root = config.get('root', None)
527 if root:
528 try:
529 self.configure_root(root, True)
530 except Exception as e:
531 raise ValueError('Unable to configure root '
532 'logger: %s' % e)
533 else:
534 disable_existing = config.pop('disable_existing_loggers', True)
535
536 logging._handlers.clear()
537 del logging._handlerList[:]
538
539 # Do formatters first - they don't refer to anything else
540 formatters = config.get('formatters', EMPTY_DICT)
541 for name in formatters:
542 try:
543 formatters[name] = self.configure_formatter(
544 formatters[name])
545 except Exception as e:
546 raise ValueError('Unable to configure '
547 'formatter %r: %s' % (name, e))
548 # Next, do filters - they don't refer to anything else, either
549 filters = config.get('filters', EMPTY_DICT)
550 for name in filters:
551 try:
552 filters[name] = self.configure_filter(filters[name])
553 except Exception as e:
554 raise ValueError('Unable to configure '
555 'filter %r: %s' % (name, e))
556
557 # Next, do handlers - they refer to formatters and filters
558 # As handlers can refer to other handlers, sort the keys
559 # to allow a deterministic order of configuration
560 handlers = config.get('handlers', EMPTY_DICT)
561 for name in sorted(handlers):
562 try:
563 handler = self.configure_handler(handlers[name])
564 handler.name = name
565 handlers[name] = handler
566 except Exception as e:
567 raise ValueError('Unable to configure handler '
568 '%r: %s' % (name, e))
569 # Next, do loggers - they refer to handlers and filters
570
571 #we don't want to lose the existing loggers,
572 #since other threads may have pointers to them.
573 #existing is set to contain all existing loggers,
574 #and as we go through the new configuration we
575 #remove any which are configured. At the end,
576 #what's left in existing is the set of loggers
577 #which were in the previous configuration but
578 #which are not in the new configuration.
579 root = logging.root
580 existing = list(root.manager.loggerDict.keys())
581 #The list needs to be sorted so that we can
582 #avoid disabling child loggers of explicitly
583 #named loggers. With a sorted list it is easier
584 #to find the child loggers.
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000585 existing.sort(key=_encoded)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000586 #We'll keep the list of existing loggers
587 #which are children of named loggers here...
588 child_loggers = []
589 #now set up the new ones...
590 loggers = config.get('loggers', EMPTY_DICT)
591 for name in loggers:
592 if name in existing:
593 i = existing.index(name)
594 prefixed = name + "."
595 pflen = len(prefixed)
596 num_existing = len(existing)
597 i = i + 1 # look at the entry after name
598 while (i < num_existing) and\
599 (existing[i][:pflen] == prefixed):
600 child_loggers.append(existing[i])
601 i = i + 1
602 existing.remove(name)
603 try:
604 self.configure_logger(name, loggers[name])
605 except Exception as e:
606 raise ValueError('Unable to configure logger '
607 '%r: %s' % (name, e))
608
609 #Disable any old loggers. There's no point deleting
610 #them as other threads may continue to hold references
611 #and by disabling them, you stop them doing any logging.
612 #However, don't disable children of named loggers, as that's
613 #probably not what was intended by the user.
614 for log in existing:
615 logger = root.manager.loggerDict[log]
616 if log in child_loggers:
617 logger.level = logging.NOTSET
618 logger.handlers = []
619 logger.propagate = True
620 elif disable_existing:
621 logger.disabled = True
622
623 # And finally, do the root logger
624 root = config.get('root', None)
625 if root:
626 try:
627 self.configure_root(root)
628 except Exception as e:
629 raise ValueError('Unable to configure root '
630 'logger: %s' % e)
631 finally:
632 logging._releaseLock()
633
634 def configure_formatter(self, config):
635 """Configure a formatter from a dictionary."""
636 if '()' in config:
637 factory = config['()'] # for use in exception handler
638 try:
639 result = self.configure_custom(config)
640 except TypeError as te:
641 if "'format'" not in str(te):
642 raise
643 #Name of parameter changed from fmt to format.
644 #Retry with old name.
645 #This is so that code can be used with older Python versions
646 #(e.g. by Django)
647 config['fmt'] = config.pop('format')
648 config['()'] = factory
649 result = self.configure_custom(config)
650 else:
651 fmt = config.get('format', None)
652 dfmt = config.get('datefmt', None)
653 result = logging.Formatter(fmt, dfmt)
654 return result
655
656 def configure_filter(self, config):
657 """Configure a filter from a dictionary."""
658 if '()' in config:
659 result = self.configure_custom(config)
660 else:
661 name = config.get('name', '')
662 result = logging.Filter(name)
663 return result
664
665 def add_filters(self, filterer, filters):
666 """Add filters to a filterer from a list of names."""
667 for f in filters:
668 try:
669 filterer.addFilter(self.config['filters'][f])
670 except Exception as e:
671 raise ValueError('Unable to add filter %r: %s' % (f, e))
672
673 def configure_handler(self, config):
674 """Configure a handler from a dictionary."""
675 formatter = config.pop('formatter', None)
676 if formatter:
677 try:
678 formatter = self.config['formatters'][formatter]
679 except Exception as e:
680 raise ValueError('Unable to set formatter '
681 '%r: %s' % (formatter, e))
682 level = config.pop('level', None)
683 filters = config.pop('filters', None)
684 if '()' in config:
685 c = config.pop('()')
686 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
687 c = self.resolve(c)
688 factory = c
689 else:
690 klass = self.resolve(config.pop('class'))
691 #Special case for handler which refers to another handler
692 if issubclass(klass, logging.handlers.MemoryHandler) and\
693 'target' in config:
694 try:
695 config['target'] = self.config['handlers'][config['target']]
696 except Exception as e:
697 raise ValueError('Unable to set target handler '
698 '%r: %s' % (config['target'], e))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000699 elif issubclass(klass, logging.handlers.SMTPHandler) and\
700 'mailhost' in config:
701 config['mailhost'] = self.as_tuple(config['mailhost'])
702 elif issubclass(klass, logging.handlers.SysLogHandler) and\
703 'address' in config:
704 config['address'] = self.as_tuple(config['address'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000705 factory = klass
706 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
707 try:
708 result = factory(**kwargs)
709 except TypeError as te:
710 if "'stream'" not in str(te):
711 raise
712 #The argument name changed from strm to stream
713 #Retry with old name.
714 #This is so that code can be used with older Python versions
715 #(e.g. by Django)
716 kwargs['strm'] = kwargs.pop('stream')
717 result = factory(**kwargs)
718 if formatter:
719 result.setFormatter(formatter)
720 if level is not None:
721 result.setLevel(logging._checkLevel(level))
722 if filters:
723 self.add_filters(result, filters)
724 return result
725
726 def add_handlers(self, logger, handlers):
727 """Add handlers to a logger from a list of names."""
728 for h in handlers:
729 try:
730 logger.addHandler(self.config['handlers'][h])
731 except Exception as e:
732 raise ValueError('Unable to add handler %r: %s' % (h, e))
733
734 def common_logger_config(self, logger, config, incremental=False):
735 """
736 Perform configuration which is common to root and non-root loggers.
737 """
738 level = config.get('level', None)
739 if level is not None:
740 logger.setLevel(logging._checkLevel(level))
741 if not incremental:
742 #Remove any existing handlers
743 for h in logger.handlers[:]:
744 logger.removeHandler(h)
745 handlers = config.get('handlers', None)
746 if handlers:
747 self.add_handlers(logger, handlers)
748 filters = config.get('filters', None)
749 if filters:
750 self.add_filters(logger, filters)
751
752 def configure_logger(self, name, config, incremental=False):
753 """Configure a non-root logger from a dictionary."""
754 logger = logging.getLogger(name)
755 self.common_logger_config(logger, config, incremental)
756 propagate = config.get('propagate', None)
757 if propagate is not None:
758 logger.propagate = propagate
759
760 def configure_root(self, config, incremental=False):
761 """Configure a root logger from a dictionary."""
762 root = logging.getLogger()
763 self.common_logger_config(root, config, incremental)
764
765dictConfigClass = DictConfigurator
766
767def dictConfig(config):
768 """Configure logging using a dictionary."""
769 dictConfigClass(config).configure()
770
771
Guido van Rossum57102f82002-11-13 16:15:58 +0000772def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
773 """
774 Start up a socket server on the specified port, and listen for new
775 configurations.
776
777 These will be sent as a file suitable for processing by fileConfig().
778 Returns a Thread object on which you can call start() to start the server,
779 and which you can join() when appropriate. To stop the server, call
780 stopListening().
781 """
782 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000783 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000784
785 class ConfigStreamHandler(StreamRequestHandler):
786 """
787 Handler for a logging configuration request.
788
789 It expects a completely new logging configuration and uses fileConfig
790 to install it.
791 """
792 def handle(self):
793 """
794 Handle a request.
795
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000796 Each request is expected to be a 4-byte length, packed using
797 struct.pack(">L", n), followed by the config file.
798 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000799 """
800 import tempfile
801 try:
802 conn = self.connection
803 chunk = conn.recv(4)
804 if len(chunk) == 4:
805 slen = struct.unpack(">L", chunk)[0]
806 chunk = self.connection.recv(slen)
807 while len(chunk) < slen:
808 chunk = chunk + conn.recv(slen - len(chunk))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000809 chunk = chunk.decode("utf-8")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000810 try:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000811 import json
812 d =json.loads(chunk)
813 assert isinstance(d, dict)
814 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000815 except:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000816 #Apply new configuration.
817
818 file = io.StringIO(chunk)
819 try:
820 fileConfig(file)
821 except (KeyboardInterrupt, SystemExit):
822 raise
823 except:
824 traceback.print_exc()
825 if self.server.ready:
826 self.server.ready.set()
Guido van Rossumb940e112007-01-10 16:19:56 +0000827 except socket.error as e:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000828 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000829 raise
830 else:
831 errcode = e.args[0]
832 if errcode != RESET_ERROR:
833 raise
834
835 class ConfigSocketReceiver(ThreadingTCPServer):
836 """
837 A simple TCP socket-based logging config receiver.
838 """
839
840 allow_reuse_address = 1
841
842 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000843 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000844 ThreadingTCPServer.__init__(self, (host, port), handler)
845 logging._acquireLock()
846 self.abort = 0
847 logging._releaseLock()
848 self.timeout = 1
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000849 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000850
851 def serve_until_stopped(self):
852 import select
853 abort = 0
854 while not abort:
855 rd, wr, ex = select.select([self.socket.fileno()],
856 [], [],
857 self.timeout)
858 if rd:
859 self.handle_request()
860 logging._acquireLock()
861 abort = self.abort
862 logging._releaseLock()
863
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000864 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000865
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000866 def __init__(self, rcvr, hdlr, port):
867 super(Server, self).__init__()
868 self.rcvr = rcvr
869 self.hdlr = hdlr
870 self.port = port
871 self.ready = threading.Event()
872
873 def run(self):
874 server = self.rcvr(port=self.port, handler=self.hdlr,
875 ready=self.ready)
876 self.ready.set()
877 global _listener
878 logging._acquireLock()
879 _listener = server
880 logging._releaseLock()
881 server.serve_until_stopped()
882
883 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000884
885def stopListening():
886 """
887 Stop the listening server which was created with a call to listen().
888 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000889 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000890 if _listener:
891 logging._acquireLock()
892 _listener.abort = 1
893 _listener = None
894 logging._releaseLock()