blob: 7a2188f2425df1d07ef31e7efd163eebfc7eca71 [file] [log] [blame]
Vinay Sajip28c382f2010-02-04 18:48:53 +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 Sajip28c382f2010-02-04 18:48:53 +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 Sajip28c382f2010-02-04 18:48:53 +000027import sys, logging, logging.handlers, socket, struct, os, traceback, re
28import types, cStringIO
Vinay Sajip612df8e2005-02-18 11:54:46 +000029
30try:
31 import thread
32 import threading
33except ImportError:
34 thread = None
Guido van Rossum57102f82002-11-13 16:15:58 +000035
Georg Brandle152a772008-05-24 18:31:28 +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
Vinay Sajip1c77b7f2009-10-10 20:32:36 +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 """
Georg Brandl392c6fc2008-05-25 07:25:25 +000062 import ConfigParser
Guido van Rossum57102f82002-11-13 16:15:58 +000063
Georg Brandl392c6fc2008-05-25 07:25:25 +000064 cp = ConfigParser.ConfigParser(defaults)
Vinay Sajip64e8b972010-02-07 12:56:54 +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()
Georg Brandlf3e30422006-08-12 08:32:02 +000076 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000077 # Handlers add themselves to logging._handlers
78 handlers = _install_handlers(cp, formatters)
Vinay Sajip5f7b97d2008-06-19 22:40:17 +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."""
Vinay Sajip1c77b7f2009-10-10 20:32:36 +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
Vinay Sajip6a2fd812008-09-03 09:20:05 +000098def _strip_spaces(alist):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +000099 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 {}
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000106 flist = flist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000107 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000108 formatters = {}
109 for form in flist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +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 {}
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000135 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +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:
Vinay Sajip6a2fd812008-09-03 09:20:05 +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 = ""
Vinay Sajipbc7e34f2008-07-18 08:59:06 +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))
Brett Cannone6bfe802008-08-04 00:09:43 +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])
Vinay Sajip5ff71712008-06-29 21:25:28 +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
Vinay Sajip5f7b97d2008-06-19 22:40:17 +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")
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000178 llist = llist.split(",")
179 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):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000192 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000193 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000194 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +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.
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000206 existing = list(root.manager.loggerDict.keys())
Vinay Sajip95dd03b2007-11-11 14:27:30 +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:
Vinay Sajip95dd03b2007-11-11 14:27:30 +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):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000244 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000245 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000246 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +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.
Vinay Sajip95dd03b2007-11-11 14:27:30 +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:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000255 logger = root.manager.loggerDict[log]
256 if log in child_loggers:
257 logger.level = logging.NOTSET
258 logger.handlers = []
259 logger.propagate = 1
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000260 elif disable_existing_loggers:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000261 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000262
263
Vinay Sajip28c382f2010-02-04 18:48:53 +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, basestring): # str for py3k
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__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
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
Vinay Sajipd45a2782010-03-06 15:12:08 +0000487 if 'version' not in config:
488 raise ValueError("dictionary doesn't specify a version")
489 if config['version'] != 1:
490 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000491 incremental = config.pop('incremental', False)
492 EMPTY_DICT = {}
493 logging._acquireLock()
494 try:
495 if incremental:
496 handlers = config.get('handlers', EMPTY_DICT)
497 for name in handlers:
498 if name not in logging._handlers:
499 raise ValueError('No handler found with '
500 'name %r' % name)
501 else:
502 try:
503 handler = logging._handlers[name]
504 handler_config = handlers[name]
505 level = handler_config.get('level', None)
506 if level:
507 handler.setLevel(logging._checkLevel(level))
508 except StandardError, e:
509 raise ValueError('Unable to configure handler '
510 '%r: %s' % (name, e))
511 loggers = config.get('loggers', EMPTY_DICT)
512 for name in loggers:
513 try:
514 self.configure_logger(name, loggers[name], True)
515 except StandardError, e:
516 raise ValueError('Unable to configure logger '
517 '%r: %s' % (name, e))
518 root = config.get('root', None)
519 if root:
520 try:
521 self.configure_root(root, True)
522 except StandardError, e:
523 raise ValueError('Unable to configure root '
524 'logger: %s' % e)
525 else:
526 disable_existing = config.pop('disable_existing_loggers', True)
527
528 logging._handlers.clear()
529 del logging._handlerList[:]
530
531 # Do formatters first - they don't refer to anything else
532 formatters = config.get('formatters', EMPTY_DICT)
533 for name in formatters:
534 try:
535 formatters[name] = self.configure_formatter(
536 formatters[name])
537 except StandardError, e:
538 raise ValueError('Unable to configure '
539 'formatter %r: %s' % (name, e))
540 # Next, do filters - they don't refer to anything else, either
541 filters = config.get('filters', EMPTY_DICT)
542 for name in filters:
543 try:
544 filters[name] = self.configure_filter(filters[name])
545 except StandardError, e:
546 raise ValueError('Unable to configure '
547 'filter %r: %s' % (name, e))
548
549 # Next, do handlers - they refer to formatters and filters
550 # As handlers can refer to other handlers, sort the keys
551 # to allow a deterministic order of configuration
552 handlers = config.get('handlers', EMPTY_DICT)
553 for name in sorted(handlers):
554 try:
555 handler = self.configure_handler(handlers[name])
556 handler.name = name
557 handlers[name] = handler
558 except StandardError, e:
559 raise ValueError('Unable to configure handler '
560 '%r: %s' % (name, e))
561 # Next, do loggers - they refer to handlers and filters
562
563 #we don't want to lose the existing loggers,
564 #since other threads may have pointers to them.
565 #existing is set to contain all existing loggers,
566 #and as we go through the new configuration we
567 #remove any which are configured. At the end,
568 #what's left in existing is the set of loggers
569 #which were in the previous configuration but
570 #which are not in the new configuration.
571 root = logging.root
572 existing = root.manager.loggerDict.keys()
573 #The list needs to be sorted so that we can
574 #avoid disabling child loggers of explicitly
575 #named loggers. With a sorted list it is easier
576 #to find the child loggers.
577 existing.sort()
578 #We'll keep the list of existing loggers
579 #which are children of named loggers here...
580 child_loggers = []
581 #now set up the new ones...
582 loggers = config.get('loggers', EMPTY_DICT)
583 for name in loggers:
584 if name in existing:
585 i = existing.index(name)
586 prefixed = name + "."
587 pflen = len(prefixed)
588 num_existing = len(existing)
589 i = i + 1 # look at the entry after name
590 while (i < num_existing) and\
591 (existing[i][:pflen] == prefixed):
592 child_loggers.append(existing[i])
593 i = i + 1
594 existing.remove(name)
595 try:
596 self.configure_logger(name, loggers[name])
597 except StandardError, e:
598 raise ValueError('Unable to configure logger '
599 '%r: %s' % (name, e))
600
601 #Disable any old loggers. There's no point deleting
602 #them as other threads may continue to hold references
603 #and by disabling them, you stop them doing any logging.
604 #However, don't disable children of named loggers, as that's
605 #probably not what was intended by the user.
606 for log in existing:
607 logger = root.manager.loggerDict[log]
608 if log in child_loggers:
609 logger.level = logging.NOTSET
610 logger.handlers = []
611 logger.propagate = True
612 elif disable_existing:
613 logger.disabled = True
614
615 # And finally, do the root logger
616 root = config.get('root', None)
617 if root:
618 try:
619 self.configure_root(root)
620 except StandardError, e:
621 raise ValueError('Unable to configure root '
622 'logger: %s' % e)
623 finally:
624 logging._releaseLock()
625
626 def configure_formatter(self, config):
627 """Configure a formatter from a dictionary."""
628 if '()' in config:
629 factory = config['()'] # for use in exception handler
630 try:
631 result = self.configure_custom(config)
632 except TypeError, te:
633 if "'format'" not in str(te):
634 raise
635 #Name of parameter changed from fmt to format.
636 #Retry with old name.
637 #This is so that code can be used with older Python versions
638 #(e.g. by Django)
639 config['fmt'] = config.pop('format')
640 config['()'] = factory
641 result = self.configure_custom(config)
642 else:
643 fmt = config.get('format', None)
644 dfmt = config.get('datefmt', None)
645 result = logging.Formatter(fmt, dfmt)
646 return result
647
648 def configure_filter(self, config):
649 """Configure a filter from a dictionary."""
650 if '()' in config:
651 result = self.configure_custom(config)
652 else:
653 name = config.get('name', '')
654 result = logging.Filter(name)
655 return result
656
657 def add_filters(self, filterer, filters):
658 """Add filters to a filterer from a list of names."""
659 for f in filters:
660 try:
661 filterer.addFilter(self.config['filters'][f])
662 except StandardError, e:
663 raise ValueError('Unable to add filter %r: %s' % (f, e))
664
665 def configure_handler(self, config):
666 """Configure a handler from a dictionary."""
667 formatter = config.pop('formatter', None)
668 if formatter:
669 try:
670 formatter = self.config['formatters'][formatter]
671 except StandardError, e:
672 raise ValueError('Unable to set formatter '
673 '%r: %s' % (formatter, e))
674 level = config.pop('level', None)
675 filters = config.pop('filters', None)
676 if '()' in config:
677 c = config.pop('()')
678 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
679 c = self.resolve(c)
680 factory = c
681 else:
682 klass = self.resolve(config.pop('class'))
683 #Special case for handler which refers to another handler
684 if issubclass(klass, logging.handlers.MemoryHandler) and\
685 'target' in config:
686 try:
687 config['target'] = self.config['handlers'][config['target']]
688 except StandardError, e:
689 raise ValueError('Unable to set target handler '
690 '%r: %s' % (config['target'], e))
691 factory = klass
692 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
693 try:
694 result = factory(**kwargs)
695 except TypeError, te:
696 if "'stream'" not in str(te):
697 raise
698 #The argument name changed from strm to stream
699 #Retry with old name.
700 #This is so that code can be used with older Python versions
701 #(e.g. by Django)
702 kwargs['strm'] = kwargs.pop('stream')
703 result = factory(**kwargs)
704 if formatter:
705 result.setFormatter(formatter)
706 if level is not None:
707 result.setLevel(logging._checkLevel(level))
708 if filters:
709 self.add_filters(result, filters)
710 return result
711
712 def add_handlers(self, logger, handlers):
713 """Add handlers to a logger from a list of names."""
714 for h in handlers:
715 try:
716 logger.addHandler(self.config['handlers'][h])
717 except StandardError, e:
718 raise ValueError('Unable to add handler %r: %s' % (h, e))
719
720 def common_logger_config(self, logger, config, incremental=False):
721 """
722 Perform configuration which is common to root and non-root loggers.
723 """
724 level = config.get('level', None)
725 if level is not None:
726 logger.setLevel(logging._checkLevel(level))
727 if not incremental:
728 #Remove any existing handlers
729 for h in logger.handlers[:]:
730 logger.removeHandler(h)
731 handlers = config.get('handlers', None)
732 if handlers:
733 self.add_handlers(logger, handlers)
734 filters = config.get('filters', None)
735 if filters:
736 self.add_filters(logger, filters)
737
738 def configure_logger(self, name, config, incremental=False):
739 """Configure a non-root logger from a dictionary."""
740 logger = logging.getLogger(name)
741 self.common_logger_config(logger, config, incremental)
742 propagate = config.get('propagate', None)
743 if propagate is not None:
744 logger.propagate = propagate
745
746 def configure_root(self, config, incremental=False):
747 """Configure a root logger from a dictionary."""
748 root = logging.getLogger()
749 self.common_logger_config(root, config, incremental)
750
751dictConfigClass = DictConfigurator
752
753def dictConfig(config):
754 """Configure logging using a dictionary."""
755 dictConfigClass(config).configure()
756
757
Guido van Rossum57102f82002-11-13 16:15:58 +0000758def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
759 """
760 Start up a socket server on the specified port, and listen for new
761 configurations.
762
763 These will be sent as a file suitable for processing by fileConfig().
764 Returns a Thread object on which you can call start() to start the server,
765 and which you can join() when appropriate. To stop the server, call
766 stopListening().
767 """
768 if not thread:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000769 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000770
771 class ConfigStreamHandler(StreamRequestHandler):
772 """
773 Handler for a logging configuration request.
774
775 It expects a completely new logging configuration and uses fileConfig
776 to install it.
777 """
778 def handle(self):
779 """
780 Handle a request.
781
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000782 Each request is expected to be a 4-byte length, packed using
783 struct.pack(">L", n), followed by the config file.
784 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000785 """
786 import tempfile
787 try:
788 conn = self.connection
789 chunk = conn.recv(4)
790 if len(chunk) == 4:
791 slen = struct.unpack(">L", chunk)[0]
792 chunk = self.connection.recv(slen)
793 while len(chunk) < slen:
794 chunk = chunk + conn.recv(slen - len(chunk))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000795 try:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000796 import json
797 d =json.loads(chunk)
798 assert isinstance(d, dict)
799 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000800 except:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000801 #Apply new configuration.
802
803 file = cStringIO.StringIO(chunk)
804 try:
805 fileConfig(file)
806 except (KeyboardInterrupt, SystemExit):
807 raise
808 except:
809 traceback.print_exc()
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000810 if self.server.ready:
811 self.server.ready.set()
Guido van Rossum57102f82002-11-13 16:15:58 +0000812 except socket.error, e:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000813 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000814 raise
815 else:
816 errcode = e.args[0]
817 if errcode != RESET_ERROR:
818 raise
819
820 class ConfigSocketReceiver(ThreadingTCPServer):
821 """
822 A simple TCP socket-based logging config receiver.
823 """
824
825 allow_reuse_address = 1
826
827 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000828 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000829 ThreadingTCPServer.__init__(self, (host, port), handler)
830 logging._acquireLock()
831 self.abort = 0
832 logging._releaseLock()
833 self.timeout = 1
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000834 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000835
836 def serve_until_stopped(self):
837 import select
838 abort = 0
839 while not abort:
840 rd, wr, ex = select.select([self.socket.fileno()],
841 [], [],
842 self.timeout)
843 if rd:
844 self.handle_request()
845 logging._acquireLock()
846 abort = self.abort
847 logging._releaseLock()
848
Benjamin Peterson239f1382010-02-06 22:08:15 +0000849 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000850
Benjamin Peterson239f1382010-02-06 22:08:15 +0000851 def __init__(self, rcvr, hdlr, port):
852 super(Server, self).__init__()
853 self.rcvr = rcvr
854 self.hdlr = hdlr
855 self.port = port
856 self.ready = threading.Event()
857
858 def run(self):
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000859 server = self.rcvr(port=self.port, handler=self.hdlr,
860 ready=self.ready)
Benjamin Peterson239f1382010-02-06 22:08:15 +0000861 self.ready.set()
862 global _listener
863 logging._acquireLock()
864 _listener = server
865 logging._releaseLock()
866 server.serve_until_stopped()
867
868 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000869
870def stopListening():
871 """
872 Stop the listening server which was created with a call to listen().
873 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000874 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000875 if _listener:
876 logging._acquireLock()
877 _listener.abort = 1
878 _listener = None
879 logging._releaseLock()