blob: 767c630bd8afb6dedfd9e58320399fe8f60409fe [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
Vinay Sajip989b69a2006-01-16 21:28:37 +0000101def _create_formatters(cp):
102 """Create and return formatters"""
103 flist = cp.get("formatters", "keys")
104 if not len(flist):
105 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000106 flist = flist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000107 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000108 formatters = {}
109 for form in flist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000110 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000111 opts = cp.options(sectname)
112 if "format" in opts:
113 fs = cp.get(sectname, "format", 1)
114 else:
115 fs = None
116 if "datefmt" in opts:
117 dfs = cp.get(sectname, "datefmt", 1)
118 else:
119 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000120 c = logging.Formatter
121 if "class" in opts:
122 class_name = cp.get(sectname, "class")
123 if class_name:
124 c = _resolve(class_name)
125 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000126 formatters[form] = f
127 return formatters
128
129
130def _install_handlers(cp, formatters):
131 """Install and return handlers"""
132 hlist = cp.get("handlers", "keys")
133 if not len(hlist):
134 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000135 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000136 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000137 handlers = {}
138 fixups = [] #for inter-handler references
139 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000140 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000141 klass = cp.get(sectname, "class")
142 opts = cp.options(sectname)
143 if "formatter" in opts:
144 fmt = cp.get(sectname, "formatter")
145 else:
146 fmt = ""
Georg Brandl3dbca812008-07-23 16:10:53 +0000147 try:
148 klass = eval(klass, vars(logging))
149 except (AttributeError, NameError):
150 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000151 args = cp.get(sectname, "args")
152 args = eval(args, vars(logging))
Neal Norwitzd9108552006-03-17 08:00:19 +0000153 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000154 if "level" in opts:
155 level = cp.get(sectname, "level")
156 h.setLevel(logging._levelNames[level])
157 if len(fmt):
158 h.setFormatter(formatters[fmt])
Benjamin Peterson41181742008-07-02 20:22:54 +0000159 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000160 if "target" in opts:
161 target = cp.get(sectname,"target")
162 else:
163 target = ""
164 if len(target): #the target handler may not be loaded yet, so keep for later...
165 fixups.append((h, target))
166 handlers[hand] = h
167 #now all handlers are loaded, fixup inter-handler references...
168 for h, t in fixups:
169 h.setTarget(handlers[t])
170 return handlers
171
172
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000173def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000174 """Create and install loggers"""
175
176 # configure the root first
177 llist = cp.get("loggers", "keys")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000178 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000179 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000180 llist.remove("root")
181 sectname = "logger_root"
182 root = logging.root
183 log = root
184 opts = cp.options(sectname)
185 if "level" in opts:
186 level = cp.get(sectname, "level")
187 log.setLevel(logging._levelNames[level])
188 for h in root.handlers[:]:
189 root.removeHandler(h)
190 hlist = cp.get(sectname, "handlers")
191 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000192 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000193 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000194 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000195 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000196
197 #and now the others...
198 #we don't want to lose the existing loggers,
199 #since other threads may have pointers to them.
200 #existing is set to contain all existing loggers,
201 #and as we go through the new configuration we
202 #remove any which are configured. At the end,
203 #what's left in existing is the set of loggers
204 #which were in the previous configuration but
205 #which are not in the new configuration.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000206 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +0000207 #The list needs to be sorted so that we can
208 #avoid disabling child loggers of explicitly
209 #named loggers. With a sorted list it is easier
210 #to find the child loggers.
211 existing.sort()
212 #We'll keep the list of existing loggers
213 #which are children of named loggers here...
214 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000215 #now set up the new ones...
216 for log in llist:
217 sectname = "logger_%s" % log
218 qn = cp.get(sectname, "qualname")
219 opts = cp.options(sectname)
220 if "propagate" in opts:
221 propagate = cp.getint(sectname, "propagate")
222 else:
223 propagate = 1
224 logger = logging.getLogger(qn)
225 if qn in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000226 i = existing.index(qn)
227 prefixed = qn + "."
228 pflen = len(prefixed)
229 num_existing = len(existing)
230 i = i + 1 # look at the entry after qn
231 while (i < num_existing) and (existing[i][:pflen] == prefixed):
232 child_loggers.append(existing[i])
233 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000234 existing.remove(qn)
235 if "level" in opts:
236 level = cp.get(sectname, "level")
237 logger.setLevel(logging._levelNames[level])
238 for h in logger.handlers[:]:
239 logger.removeHandler(h)
240 logger.propagate = propagate
241 logger.disabled = 0
242 hlist = cp.get(sectname, "handlers")
243 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000244 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000245 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000246 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000247 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000248
249 #Disable any old loggers. There's no point deleting
250 #them as other threads may continue to hold references
251 #and by disabling them, you stop them doing any logging.
Christian Heimes96f31632007-11-12 01:32:03 +0000252 #However, don't disable children of named loggers, as that's
253 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000254 for log in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000255 logger = root.manager.loggerDict[log]
256 if log in child_loggers:
257 logger.level = logging.NOTSET
258 logger.handlers = []
259 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000260 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000261 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000262
263
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000264
265IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
266
267
268def valid_ident(s):
269 m = IDENTIFIER.match(s)
270 if not m:
271 raise ValueError('Not a valid Python identifier: %r' % s)
272 return True
273
274
275# The ConvertingXXX classes are wrappers around standard Python containers,
276# and they serve to convert any suitable values in the container. The
277# conversion converts base dicts, lists and tuples to their wrapped
278# equivalents, whereas strings which match a conversion format are converted
279# appropriately.
280#
281# Each wrapper should have a configurator attribute holding the actual
282# configurator to use for conversion.
283
284class ConvertingDict(dict):
285 """A converting dictionary wrapper."""
286
287 def __getitem__(self, key):
288 value = dict.__getitem__(self, key)
289 result = self.configurator.convert(value)
290 #If the converted value is different, save for next time
291 if value is not result:
292 self[key] = result
293 if type(result) in (ConvertingDict, ConvertingList,
294 ConvertingTuple):
295 result.parent = self
296 result.key = key
297 return result
298
299 def get(self, key, default=None):
300 value = dict.get(self, key, default)
301 result = self.configurator.convert(value)
302 #If the converted value is different, save for next time
303 if value is not result:
304 self[key] = result
305 if type(result) in (ConvertingDict, ConvertingList,
306 ConvertingTuple):
307 result.parent = self
308 result.key = key
309 return result
310
311 def pop(self, key, default=None):
312 value = dict.pop(self, key, default)
313 result = self.configurator.convert(value)
314 if value is not result:
315 if type(result) in (ConvertingDict, ConvertingList,
316 ConvertingTuple):
317 result.parent = self
318 result.key = key
319 return result
320
321class ConvertingList(list):
322 """A converting list wrapper."""
323 def __getitem__(self, key):
324 value = list.__getitem__(self, key)
325 result = self.configurator.convert(value)
326 #If the converted value is different, save for next time
327 if value is not result:
328 self[key] = result
329 if type(result) in (ConvertingDict, ConvertingList,
330 ConvertingTuple):
331 result.parent = self
332 result.key = key
333 return result
334
335 def pop(self, idx=-1):
336 value = list.pop(self, idx)
337 result = self.configurator.convert(value)
338 if value is not result:
339 if type(result) in (ConvertingDict, ConvertingList,
340 ConvertingTuple):
341 result.parent = self
342 return result
343
344class ConvertingTuple(tuple):
345 """A converting tuple wrapper."""
346 def __getitem__(self, key):
347 value = tuple.__getitem__(self, key)
348 result = self.configurator.convert(value)
349 if value is not result:
350 if type(result) in (ConvertingDict, ConvertingList,
351 ConvertingTuple):
352 result.parent = self
353 result.key = key
354 return result
355
356class BaseConfigurator(object):
357 """
358 The configurator base class which defines some useful defaults.
359 """
360
361 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
362
363 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
364 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
365 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
366 DIGIT_PATTERN = re.compile(r'^\d+$')
367
368 value_converters = {
369 'ext' : 'ext_convert',
370 'cfg' : 'cfg_convert',
371 }
372
373 # We might want to use a different one, e.g. importlib
374 importer = __import__
375
376 def __init__(self, config):
377 self.config = ConvertingDict(config)
378 self.config.configurator = self
379
380 def resolve(self, s):
381 """
382 Resolve strings to objects using standard import and attribute
383 syntax.
384 """
385 name = s.split('.')
386 used = name.pop(0)
387 found = self.importer(used)
388 for frag in name:
389 used += '.' + frag
390 try:
391 found = getattr(found, frag)
392 except AttributeError:
393 self.importer(used)
394 found = getattr(found, frag)
395 return found
396
397 def ext_convert(self, value):
398 """Default converter for the ext:// protocol."""
399 return self.resolve(value)
400
401 def cfg_convert(self, value):
402 """Default converter for the cfg:// protocol."""
403 rest = value
404 m = self.WORD_PATTERN.match(rest)
405 if m is None:
406 raise ValueError("Unable to convert %r" % value)
407 else:
408 rest = rest[m.end():]
409 d = self.config[m.groups()[0]]
410 #print d, rest
411 while rest:
412 m = self.DOT_PATTERN.match(rest)
413 if m:
414 d = d[m.groups()[0]]
415 else:
416 m = self.INDEX_PATTERN.match(rest)
417 if m:
418 idx = m.groups()[0]
419 if not self.DIGIT_PATTERN.match(idx):
420 d = d[idx]
421 else:
422 try:
423 n = int(idx) # try as number first (most likely)
424 d = d[n]
425 except TypeError:
426 d = d[idx]
427 if m:
428 rest = rest[m.end():]
429 else:
430 raise ValueError('Unable to convert '
431 '%r at %r' % (value, rest))
432 #rest should be empty
433 return d
434
435 def convert(self, value):
436 """
437 Convert values to an appropriate type. dicts, lists and tuples are
438 replaced by their converting alternatives. Strings are checked to
439 see if they have a conversion format and are converted if they do.
440 """
441 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
442 value = ConvertingDict(value)
443 value.configurator = self
444 elif not isinstance(value, ConvertingList) and isinstance(value, list):
445 value = ConvertingList(value)
446 value.configurator = self
447 elif not isinstance(value, ConvertingTuple) and\
448 isinstance(value, tuple):
449 value = ConvertingTuple(value)
450 value.configurator = self
451 elif isinstance(value, str):
452 m = self.CONVERT_PATTERN.match(value)
453 if m:
454 d = m.groupdict()
455 prefix = d['prefix']
456 converter = self.value_converters.get(prefix, None)
457 if converter:
458 suffix = d['suffix']
459 converter = getattr(self, converter)
460 value = converter(suffix)
461 return value
462
463 def configure_custom(self, config):
464 """Configure an object with a user-supplied factory."""
465 c = config.pop('()')
466 if not hasattr(c, '__call__'):
467 c = self.resolve(c)
468 props = config.pop('.', None)
469 # Check for valid identifiers
470 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
471 result = c(**kwargs)
472 if props:
473 for name, value in props.items():
474 setattr(result, name, value)
475 return result
476
477class DictConfigurator(BaseConfigurator):
478 """
479 Configure logging using a dictionary-like object to describe the
480 configuration.
481 """
482
483 def configure(self):
484 """Do the configuration."""
485
486 config = self.config
487 incremental = config.pop('incremental', False)
488 EMPTY_DICT = {}
489 logging._acquireLock()
490 try:
491 if incremental:
492 handlers = config.get('handlers', EMPTY_DICT)
493 for name in handlers:
494 if name not in logging._handlers:
495 raise ValueError('No handler found with '
496 'name %r' % name)
497 else:
498 try:
499 handler = logging._handlers[name]
500 handler_config = handlers[name]
501 level = handler_config.get('level', None)
502 if level:
503 handler.setLevel(logging._checkLevel(level))
504 except Exception as e:
505 raise ValueError('Unable to configure handler '
506 '%r: %s' % (name, e))
507 loggers = config.get('loggers', EMPTY_DICT)
508 for name in loggers:
509 try:
510 self.configure_logger(name, loggers[name], True)
511 except Exception as e:
512 raise ValueError('Unable to configure logger '
513 '%r: %s' % (name, e))
514 root = config.get('root', None)
515 if root:
516 try:
517 self.configure_root(root, True)
518 except Exception as e:
519 raise ValueError('Unable to configure root '
520 'logger: %s' % e)
521 else:
522 disable_existing = config.pop('disable_existing_loggers', True)
523
524 logging._handlers.clear()
525 del logging._handlerList[:]
526
527 # Do formatters first - they don't refer to anything else
528 formatters = config.get('formatters', EMPTY_DICT)
529 for name in formatters:
530 try:
531 formatters[name] = self.configure_formatter(
532 formatters[name])
533 except Exception as e:
534 raise ValueError('Unable to configure '
535 'formatter %r: %s' % (name, e))
536 # Next, do filters - they don't refer to anything else, either
537 filters = config.get('filters', EMPTY_DICT)
538 for name in filters:
539 try:
540 filters[name] = self.configure_filter(filters[name])
541 except Exception as e:
542 raise ValueError('Unable to configure '
543 'filter %r: %s' % (name, e))
544
545 # Next, do handlers - they refer to formatters and filters
546 # As handlers can refer to other handlers, sort the keys
547 # to allow a deterministic order of configuration
548 handlers = config.get('handlers', EMPTY_DICT)
549 for name in sorted(handlers):
550 try:
551 handler = self.configure_handler(handlers[name])
552 handler.name = name
553 handlers[name] = handler
554 except Exception as e:
555 raise ValueError('Unable to configure handler '
556 '%r: %s' % (name, e))
557 # Next, do loggers - they refer to handlers and filters
558
559 #we don't want to lose the existing loggers,
560 #since other threads may have pointers to them.
561 #existing is set to contain all existing loggers,
562 #and as we go through the new configuration we
563 #remove any which are configured. At the end,
564 #what's left in existing is the set of loggers
565 #which were in the previous configuration but
566 #which are not in the new configuration.
567 root = logging.root
568 existing = list(root.manager.loggerDict.keys())
569 #The list needs to be sorted so that we can
570 #avoid disabling child loggers of explicitly
571 #named loggers. With a sorted list it is easier
572 #to find the child loggers.
573 existing.sort()
574 #We'll keep the list of existing loggers
575 #which are children of named loggers here...
576 child_loggers = []
577 #now set up the new ones...
578 loggers = config.get('loggers', EMPTY_DICT)
579 for name in loggers:
580 if name in existing:
581 i = existing.index(name)
582 prefixed = name + "."
583 pflen = len(prefixed)
584 num_existing = len(existing)
585 i = i + 1 # look at the entry after name
586 while (i < num_existing) and\
587 (existing[i][:pflen] == prefixed):
588 child_loggers.append(existing[i])
589 i = i + 1
590 existing.remove(name)
591 try:
592 self.configure_logger(name, loggers[name])
593 except Exception as e:
594 raise ValueError('Unable to configure logger '
595 '%r: %s' % (name, e))
596
597 #Disable any old loggers. There's no point deleting
598 #them as other threads may continue to hold references
599 #and by disabling them, you stop them doing any logging.
600 #However, don't disable children of named loggers, as that's
601 #probably not what was intended by the user.
602 for log in existing:
603 logger = root.manager.loggerDict[log]
604 if log in child_loggers:
605 logger.level = logging.NOTSET
606 logger.handlers = []
607 logger.propagate = True
608 elif disable_existing:
609 logger.disabled = True
610
611 # And finally, do the root logger
612 root = config.get('root', None)
613 if root:
614 try:
615 self.configure_root(root)
616 except Exception as e:
617 raise ValueError('Unable to configure root '
618 'logger: %s' % e)
619 finally:
620 logging._releaseLock()
621
622 def configure_formatter(self, config):
623 """Configure a formatter from a dictionary."""
624 if '()' in config:
625 factory = config['()'] # for use in exception handler
626 try:
627 result = self.configure_custom(config)
628 except TypeError as te:
629 if "'format'" not in str(te):
630 raise
631 #Name of parameter changed from fmt to format.
632 #Retry with old name.
633 #This is so that code can be used with older Python versions
634 #(e.g. by Django)
635 config['fmt'] = config.pop('format')
636 config['()'] = factory
637 result = self.configure_custom(config)
638 else:
639 fmt = config.get('format', None)
640 dfmt = config.get('datefmt', None)
641 result = logging.Formatter(fmt, dfmt)
642 return result
643
644 def configure_filter(self, config):
645 """Configure a filter from a dictionary."""
646 if '()' in config:
647 result = self.configure_custom(config)
648 else:
649 name = config.get('name', '')
650 result = logging.Filter(name)
651 return result
652
653 def add_filters(self, filterer, filters):
654 """Add filters to a filterer from a list of names."""
655 for f in filters:
656 try:
657 filterer.addFilter(self.config['filters'][f])
658 except Exception as e:
659 raise ValueError('Unable to add filter %r: %s' % (f, e))
660
661 def configure_handler(self, config):
662 """Configure a handler from a dictionary."""
663 formatter = config.pop('formatter', None)
664 if formatter:
665 try:
666 formatter = self.config['formatters'][formatter]
667 except Exception as e:
668 raise ValueError('Unable to set formatter '
669 '%r: %s' % (formatter, e))
670 level = config.pop('level', None)
671 filters = config.pop('filters', None)
672 if '()' in config:
673 c = config.pop('()')
674 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
675 c = self.resolve(c)
676 factory = c
677 else:
678 klass = self.resolve(config.pop('class'))
679 #Special case for handler which refers to another handler
680 if issubclass(klass, logging.handlers.MemoryHandler) and\
681 'target' in config:
682 try:
683 config['target'] = self.config['handlers'][config['target']]
684 except Exception as e:
685 raise ValueError('Unable to set target handler '
686 '%r: %s' % (config['target'], e))
687 factory = klass
688 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
689 try:
690 result = factory(**kwargs)
691 except TypeError as te:
692 if "'stream'" not in str(te):
693 raise
694 #The argument name changed from strm to stream
695 #Retry with old name.
696 #This is so that code can be used with older Python versions
697 #(e.g. by Django)
698 kwargs['strm'] = kwargs.pop('stream')
699 result = factory(**kwargs)
700 if formatter:
701 result.setFormatter(formatter)
702 if level is not None:
703 result.setLevel(logging._checkLevel(level))
704 if filters:
705 self.add_filters(result, filters)
706 return result
707
708 def add_handlers(self, logger, handlers):
709 """Add handlers to a logger from a list of names."""
710 for h in handlers:
711 try:
712 logger.addHandler(self.config['handlers'][h])
713 except Exception as e:
714 raise ValueError('Unable to add handler %r: %s' % (h, e))
715
716 def common_logger_config(self, logger, config, incremental=False):
717 """
718 Perform configuration which is common to root and non-root loggers.
719 """
720 level = config.get('level', None)
721 if level is not None:
722 logger.setLevel(logging._checkLevel(level))
723 if not incremental:
724 #Remove any existing handlers
725 for h in logger.handlers[:]:
726 logger.removeHandler(h)
727 handlers = config.get('handlers', None)
728 if handlers:
729 self.add_handlers(logger, handlers)
730 filters = config.get('filters', None)
731 if filters:
732 self.add_filters(logger, filters)
733
734 def configure_logger(self, name, config, incremental=False):
735 """Configure a non-root logger from a dictionary."""
736 logger = logging.getLogger(name)
737 self.common_logger_config(logger, config, incremental)
738 propagate = config.get('propagate', None)
739 if propagate is not None:
740 logger.propagate = propagate
741
742 def configure_root(self, config, incremental=False):
743 """Configure a root logger from a dictionary."""
744 root = logging.getLogger()
745 self.common_logger_config(root, config, incremental)
746
747dictConfigClass = DictConfigurator
748
749def dictConfig(config):
750 """Configure logging using a dictionary."""
751 dictConfigClass(config).configure()
752
753
Guido van Rossum57102f82002-11-13 16:15:58 +0000754def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
755 """
756 Start up a socket server on the specified port, and listen for new
757 configurations.
758
759 These will be sent as a file suitable for processing by fileConfig().
760 Returns a Thread object on which you can call start() to start the server,
761 and which you can join() when appropriate. To stop the server, call
762 stopListening().
763 """
764 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000765 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000766
767 class ConfigStreamHandler(StreamRequestHandler):
768 """
769 Handler for a logging configuration request.
770
771 It expects a completely new logging configuration and uses fileConfig
772 to install it.
773 """
774 def handle(self):
775 """
776 Handle a request.
777
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000778 Each request is expected to be a 4-byte length, packed using
779 struct.pack(">L", n), followed by the config file.
780 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000781 """
782 import tempfile
783 try:
784 conn = self.connection
785 chunk = conn.recv(4)
786 if len(chunk) == 4:
787 slen = struct.unpack(">L", chunk)[0]
788 chunk = self.connection.recv(slen)
789 while len(chunk) < slen:
790 chunk = chunk + conn.recv(slen - len(chunk))
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000791 chunk = chunk.decode('utf-8')
Vinay Sajip989b69a2006-01-16 21:28:37 +0000792 try:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000793 import json
794 d =json.loads(chunk)
795 assert isinstance(d, dict)
796 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000797 except:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000798 #Apply new configuration.
799
800 file = io.StringIO(chunk)
801 try:
802 fileConfig(file)
803 except (KeyboardInterrupt, SystemExit):
804 raise
805 except:
806 traceback.print_exc()
807 if self.server.ready:
808 self.server.ready.set()
Guido van Rossumb940e112007-01-10 16:19:56 +0000809 except socket.error as e:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000810 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000811 raise
812 else:
813 errcode = e.args[0]
814 if errcode != RESET_ERROR:
815 raise
816
817 class ConfigSocketReceiver(ThreadingTCPServer):
818 """
819 A simple TCP socket-based logging config receiver.
820 """
821
822 allow_reuse_address = 1
823
824 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000825 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000826 ThreadingTCPServer.__init__(self, (host, port), handler)
827 logging._acquireLock()
828 self.abort = 0
829 logging._releaseLock()
830 self.timeout = 1
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000831 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000832
833 def serve_until_stopped(self):
834 import select
835 abort = 0
836 while not abort:
837 rd, wr, ex = select.select([self.socket.fileno()],
838 [], [],
839 self.timeout)
840 if rd:
841 self.handle_request()
842 logging._acquireLock()
843 abort = self.abort
844 logging._releaseLock()
845
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000846 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000847
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000848 def __init__(self, rcvr, hdlr, port):
849 super(Server, self).__init__()
850 self.rcvr = rcvr
851 self.hdlr = hdlr
852 self.port = port
853 self.ready = threading.Event()
854
855 def run(self):
856 server = self.rcvr(port=self.port, handler=self.hdlr,
857 ready=self.ready)
858 self.ready.set()
859 global _listener
860 logging._acquireLock()
861 _listener = server
862 logging._releaseLock()
863 server.serve_until_stopped()
864
865 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000866
867def stopListening():
868 """
869 Stop the listening server which was created with a call to listen().
870 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000871 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000872 if _listener:
873 logging._acquireLock()
874 _listener.abort = 1
875 _listener = None
876 logging._releaseLock()