blob: 0dbcd8f763a2ae9f26d5d75b8ea09728ead16e82 [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).
61 In versions of ConfigParser which have the readfp method [typically
62 shipped in 2.x versions of Python], you can pass in a file-like object
63 rather than a filename, in which case the file-like object will be read
64 using readfp.
65 """
Georg Brandl392c6fc2008-05-25 07:25:25 +000066 import ConfigParser
Guido van Rossum57102f82002-11-13 16:15:58 +000067
Vinay Sajip28c382f2010-02-04 18:48:53 +000068 print >> open('/tmp/tmp.txt', 'w'), fname.getvalue()
Georg Brandl392c6fc2008-05-25 07:25:25 +000069 cp = ConfigParser.ConfigParser(defaults)
Guido van Rossum57102f82002-11-13 16:15:58 +000070 if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
71 cp.readfp(fname)
72 else:
73 cp.read(fname)
Vinay Sajip989b69a2006-01-16 21:28:37 +000074
75 formatters = _create_formatters(cp)
76
77 # critical section
Guido van Rossum57102f82002-11-13 16:15:58 +000078 logging._acquireLock()
79 try:
Vinay Sajip989b69a2006-01-16 21:28:37 +000080 logging._handlers.clear()
Georg Brandlf3e30422006-08-12 08:32:02 +000081 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000082 # Handlers add themselves to logging._handlers
83 handlers = _install_handlers(cp, formatters)
Vinay Sajip5f7b97d2008-06-19 22:40:17 +000084 _install_loggers(cp, handlers, disable_existing_loggers)
Guido van Rossum57102f82002-11-13 16:15:58 +000085 finally:
86 logging._releaseLock()
87
Vinay Sajip989b69a2006-01-16 21:28:37 +000088
Vinay Sajip7a7160b2006-01-20 18:28:03 +000089def _resolve(name):
90 """Resolve a dotted name to a global object."""
Vinay Sajip1c77b7f2009-10-10 20:32:36 +000091 name = name.split('.')
Vinay Sajip7a7160b2006-01-20 18:28:03 +000092 used = name.pop(0)
93 found = __import__(used)
94 for n in name:
95 used = used + '.' + n
96 try:
97 found = getattr(found, n)
98 except AttributeError:
99 __import__(used)
100 found = getattr(found, n)
101 return found
102
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000103def _strip_spaces(alist):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000104 return map(lambda x: x.strip(), alist)
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000105
Vinay Sajip989b69a2006-01-16 21:28:37 +0000106def _create_formatters(cp):
107 """Create and return formatters"""
108 flist = cp.get("formatters", "keys")
109 if not len(flist):
110 return {}
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000111 flist = flist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000112 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000113 formatters = {}
114 for form in flist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000115 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000116 opts = cp.options(sectname)
117 if "format" in opts:
118 fs = cp.get(sectname, "format", 1)
119 else:
120 fs = None
121 if "datefmt" in opts:
122 dfs = cp.get(sectname, "datefmt", 1)
123 else:
124 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000125 c = logging.Formatter
126 if "class" in opts:
127 class_name = cp.get(sectname, "class")
128 if class_name:
129 c = _resolve(class_name)
130 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000131 formatters[form] = f
132 return formatters
133
134
135def _install_handlers(cp, formatters):
136 """Install and return handlers"""
137 hlist = cp.get("handlers", "keys")
138 if not len(hlist):
139 return {}
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000140 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000141 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000142 handlers = {}
143 fixups = [] #for inter-handler references
144 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000145 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000146 klass = cp.get(sectname, "class")
147 opts = cp.options(sectname)
148 if "formatter" in opts:
149 fmt = cp.get(sectname, "formatter")
150 else:
151 fmt = ""
Vinay Sajipbc7e34f2008-07-18 08:59:06 +0000152 try:
153 klass = eval(klass, vars(logging))
154 except (AttributeError, NameError):
155 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000156 args = cp.get(sectname, "args")
157 args = eval(args, vars(logging))
Brett Cannone6bfe802008-08-04 00:09:43 +0000158 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000159 if "level" in opts:
160 level = cp.get(sectname, "level")
161 h.setLevel(logging._levelNames[level])
162 if len(fmt):
163 h.setFormatter(formatters[fmt])
Vinay Sajip5ff71712008-06-29 21:25:28 +0000164 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000165 if "target" in opts:
166 target = cp.get(sectname,"target")
167 else:
168 target = ""
169 if len(target): #the target handler may not be loaded yet, so keep for later...
170 fixups.append((h, target))
171 handlers[hand] = h
172 #now all handlers are loaded, fixup inter-handler references...
173 for h, t in fixups:
174 h.setTarget(handlers[t])
175 return handlers
176
177
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000178def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000179 """Create and install loggers"""
180
181 # configure the root first
182 llist = cp.get("loggers", "keys")
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000183 llist = llist.split(",")
184 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000185 llist.remove("root")
186 sectname = "logger_root"
187 root = logging.root
188 log = root
189 opts = cp.options(sectname)
190 if "level" in opts:
191 level = cp.get(sectname, "level")
192 log.setLevel(logging._levelNames[level])
193 for h in root.handlers[:]:
194 root.removeHandler(h)
195 hlist = cp.get(sectname, "handlers")
196 if len(hlist):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000197 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000198 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000199 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000200 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000201
202 #and now the others...
203 #we don't want to lose the existing loggers,
204 #since other threads may have pointers to them.
205 #existing is set to contain all existing loggers,
206 #and as we go through the new configuration we
207 #remove any which are configured. At the end,
208 #what's left in existing is the set of loggers
209 #which were in the previous configuration but
210 #which are not in the new configuration.
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000211 existing = list(root.manager.loggerDict.keys())
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000212 #The list needs to be sorted so that we can
213 #avoid disabling child loggers of explicitly
214 #named loggers. With a sorted list it is easier
215 #to find the child loggers.
216 existing.sort()
217 #We'll keep the list of existing loggers
218 #which are children of named loggers here...
219 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000220 #now set up the new ones...
221 for log in llist:
222 sectname = "logger_%s" % log
223 qn = cp.get(sectname, "qualname")
224 opts = cp.options(sectname)
225 if "propagate" in opts:
226 propagate = cp.getint(sectname, "propagate")
227 else:
228 propagate = 1
229 logger = logging.getLogger(qn)
230 if qn in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000231 i = existing.index(qn)
232 prefixed = qn + "."
233 pflen = len(prefixed)
234 num_existing = len(existing)
235 i = i + 1 # look at the entry after qn
236 while (i < num_existing) and (existing[i][:pflen] == prefixed):
237 child_loggers.append(existing[i])
238 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000239 existing.remove(qn)
240 if "level" in opts:
241 level = cp.get(sectname, "level")
242 logger.setLevel(logging._levelNames[level])
243 for h in logger.handlers[:]:
244 logger.removeHandler(h)
245 logger.propagate = propagate
246 logger.disabled = 0
247 hlist = cp.get(sectname, "handlers")
248 if len(hlist):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000249 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000250 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000251 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000252 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000253
254 #Disable any old loggers. There's no point deleting
255 #them as other threads may continue to hold references
256 #and by disabling them, you stop them doing any logging.
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000257 #However, don't disable children of named loggers, as that's
258 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000259 for log in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000260 logger = root.manager.loggerDict[log]
261 if log in child_loggers:
262 logger.level = logging.NOTSET
263 logger.handlers = []
264 logger.propagate = 1
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000265 elif disable_existing_loggers:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000266 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000267
268
Vinay Sajip28c382f2010-02-04 18:48:53 +0000269
270IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
271
272
273def valid_ident(s):
274 m = IDENTIFIER.match(s)
275 if not m:
276 raise ValueError('Not a valid Python identifier: %r' % s)
277 return True
278
279
280# The ConvertingXXX classes are wrappers around standard Python containers,
281# and they serve to convert any suitable values in the container. The
282# conversion converts base dicts, lists and tuples to their wrapped
283# equivalents, whereas strings which match a conversion format are converted
284# appropriately.
285#
286# Each wrapper should have a configurator attribute holding the actual
287# configurator to use for conversion.
288
289class ConvertingDict(dict):
290 """A converting dictionary wrapper."""
291
292 def __getitem__(self, key):
293 value = dict.__getitem__(self, key)
294 result = self.configurator.convert(value)
295 #If the converted value is different, save for next time
296 if value is not result:
297 self[key] = result
298 if type(result) in (ConvertingDict, ConvertingList,
299 ConvertingTuple):
300 result.parent = self
301 result.key = key
302 return result
303
304 def get(self, key, default=None):
305 value = dict.get(self, key, default)
306 result = self.configurator.convert(value)
307 #If the converted value is different, save for next time
308 if value is not result:
309 self[key] = result
310 if type(result) in (ConvertingDict, ConvertingList,
311 ConvertingTuple):
312 result.parent = self
313 result.key = key
314 return result
315
316 def pop(self, key, default=None):
317 value = dict.pop(self, key, default)
318 result = self.configurator.convert(value)
319 if value is not result:
320 if type(result) in (ConvertingDict, ConvertingList,
321 ConvertingTuple):
322 result.parent = self
323 result.key = key
324 return result
325
326class ConvertingList(list):
327 """A converting list wrapper."""
328 def __getitem__(self, key):
329 value = list.__getitem__(self, key)
330 result = self.configurator.convert(value)
331 #If the converted value is different, save for next time
332 if value is not result:
333 self[key] = result
334 if type(result) in (ConvertingDict, ConvertingList,
335 ConvertingTuple):
336 result.parent = self
337 result.key = key
338 return result
339
340 def pop(self, idx=-1):
341 value = list.pop(self, idx)
342 result = self.configurator.convert(value)
343 if value is not result:
344 if type(result) in (ConvertingDict, ConvertingList,
345 ConvertingTuple):
346 result.parent = self
347 return result
348
349class ConvertingTuple(tuple):
350 """A converting tuple wrapper."""
351 def __getitem__(self, key):
352 value = tuple.__getitem__(self, key)
353 result = self.configurator.convert(value)
354 if value is not result:
355 if type(result) in (ConvertingDict, ConvertingList,
356 ConvertingTuple):
357 result.parent = self
358 result.key = key
359 return result
360
361class BaseConfigurator(object):
362 """
363 The configurator base class which defines some useful defaults.
364 """
365
366 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
367
368 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
369 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
370 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
371 DIGIT_PATTERN = re.compile(r'^\d+$')
372
373 value_converters = {
374 'ext' : 'ext_convert',
375 'cfg' : 'cfg_convert',
376 }
377
378 # We might want to use a different one, e.g. importlib
379 importer = __import__
380
381 def __init__(self, config):
382 self.config = ConvertingDict(config)
383 self.config.configurator = self
384
385 def resolve(self, s):
386 """
387 Resolve strings to objects using standard import and attribute
388 syntax.
389 """
390 name = s.split('.')
391 used = name.pop(0)
392 found = self.importer(used)
393 for frag in name:
394 used += '.' + frag
395 try:
396 found = getattr(found, frag)
397 except AttributeError:
398 self.importer(used)
399 found = getattr(found, frag)
400 return found
401
402 def ext_convert(self, value):
403 """Default converter for the ext:// protocol."""
404 return self.resolve(value)
405
406 def cfg_convert(self, value):
407 """Default converter for the cfg:// protocol."""
408 rest = value
409 m = self.WORD_PATTERN.match(rest)
410 if m is None:
411 raise ValueError("Unable to convert %r" % value)
412 else:
413 rest = rest[m.end():]
414 d = self.config[m.groups()[0]]
415 #print d, rest
416 while rest:
417 m = self.DOT_PATTERN.match(rest)
418 if m:
419 d = d[m.groups()[0]]
420 else:
421 m = self.INDEX_PATTERN.match(rest)
422 if m:
423 idx = m.groups()[0]
424 if not self.DIGIT_PATTERN.match(idx):
425 d = d[idx]
426 else:
427 try:
428 n = int(idx) # try as number first (most likely)
429 d = d[n]
430 except TypeError:
431 d = d[idx]
432 if m:
433 rest = rest[m.end():]
434 else:
435 raise ValueError('Unable to convert '
436 '%r at %r' % (value, rest))
437 #rest should be empty
438 return d
439
440 def convert(self, value):
441 """
442 Convert values to an appropriate type. dicts, lists and tuples are
443 replaced by their converting alternatives. Strings are checked to
444 see if they have a conversion format and are converted if they do.
445 """
446 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
447 value = ConvertingDict(value)
448 value.configurator = self
449 elif not isinstance(value, ConvertingList) and isinstance(value, list):
450 value = ConvertingList(value)
451 value.configurator = self
452 elif not isinstance(value, ConvertingTuple) and\
453 isinstance(value, tuple):
454 value = ConvertingTuple(value)
455 value.configurator = self
456 elif isinstance(value, basestring): # str for py3k
457 m = self.CONVERT_PATTERN.match(value)
458 if m:
459 d = m.groupdict()
460 prefix = d['prefix']
461 converter = self.value_converters.get(prefix, None)
462 if converter:
463 suffix = d['suffix']
464 converter = getattr(self, converter)
465 value = converter(suffix)
466 return value
467
468 def configure_custom(self, config):
469 """Configure an object with a user-supplied factory."""
470 c = config.pop('()')
471 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
472 c = self.resolve(c)
473 props = config.pop('.', None)
474 # Check for valid identifiers
475 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
476 result = c(**kwargs)
477 if props:
478 for name, value in props.items():
479 setattr(result, name, value)
480 return result
481
482class DictConfigurator(BaseConfigurator):
483 """
484 Configure logging using a dictionary-like object to describe the
485 configuration.
486 """
487
488 def configure(self):
489 """Do the configuration."""
490
491 config = self.config
492 incremental = config.pop('incremental', False)
493 EMPTY_DICT = {}
494 logging._acquireLock()
495 try:
496 if incremental:
497 handlers = config.get('handlers', EMPTY_DICT)
498 for name in handlers:
499 if name not in logging._handlers:
500 raise ValueError('No handler found with '
501 'name %r' % name)
502 else:
503 try:
504 handler = logging._handlers[name]
505 handler_config = handlers[name]
506 level = handler_config.get('level', None)
507 if level:
508 handler.setLevel(logging._checkLevel(level))
509 except StandardError, e:
510 raise ValueError('Unable to configure handler '
511 '%r: %s' % (name, e))
512 loggers = config.get('loggers', EMPTY_DICT)
513 for name in loggers:
514 try:
515 self.configure_logger(name, loggers[name], True)
516 except StandardError, e:
517 raise ValueError('Unable to configure logger '
518 '%r: %s' % (name, e))
519 root = config.get('root', None)
520 if root:
521 try:
522 self.configure_root(root, True)
523 except StandardError, e:
524 raise ValueError('Unable to configure root '
525 'logger: %s' % e)
526 else:
527 disable_existing = config.pop('disable_existing_loggers', True)
528
529 logging._handlers.clear()
530 del logging._handlerList[:]
531
532 # Do formatters first - they don't refer to anything else
533 formatters = config.get('formatters', EMPTY_DICT)
534 for name in formatters:
535 try:
536 formatters[name] = self.configure_formatter(
537 formatters[name])
538 except StandardError, e:
539 raise ValueError('Unable to configure '
540 'formatter %r: %s' % (name, e))
541 # Next, do filters - they don't refer to anything else, either
542 filters = config.get('filters', EMPTY_DICT)
543 for name in filters:
544 try:
545 filters[name] = self.configure_filter(filters[name])
546 except StandardError, e:
547 raise ValueError('Unable to configure '
548 'filter %r: %s' % (name, e))
549
550 # Next, do handlers - they refer to formatters and filters
551 # As handlers can refer to other handlers, sort the keys
552 # to allow a deterministic order of configuration
553 handlers = config.get('handlers', EMPTY_DICT)
554 for name in sorted(handlers):
555 try:
556 handler = self.configure_handler(handlers[name])
557 handler.name = name
558 handlers[name] = handler
559 except StandardError, e:
560 raise ValueError('Unable to configure handler '
561 '%r: %s' % (name, e))
562 # Next, do loggers - they refer to handlers and filters
563
564 #we don't want to lose the existing loggers,
565 #since other threads may have pointers to them.
566 #existing is set to contain all existing loggers,
567 #and as we go through the new configuration we
568 #remove any which are configured. At the end,
569 #what's left in existing is the set of loggers
570 #which were in the previous configuration but
571 #which are not in the new configuration.
572 root = logging.root
573 existing = root.manager.loggerDict.keys()
574 #The list needs to be sorted so that we can
575 #avoid disabling child loggers of explicitly
576 #named loggers. With a sorted list it is easier
577 #to find the child loggers.
578 existing.sort()
579 #We'll keep the list of existing loggers
580 #which are children of named loggers here...
581 child_loggers = []
582 #now set up the new ones...
583 loggers = config.get('loggers', EMPTY_DICT)
584 for name in loggers:
585 if name in existing:
586 i = existing.index(name)
587 prefixed = name + "."
588 pflen = len(prefixed)
589 num_existing = len(existing)
590 i = i + 1 # look at the entry after name
591 while (i < num_existing) and\
592 (existing[i][:pflen] == prefixed):
593 child_loggers.append(existing[i])
594 i = i + 1
595 existing.remove(name)
596 try:
597 self.configure_logger(name, loggers[name])
598 except StandardError, e:
599 raise ValueError('Unable to configure logger '
600 '%r: %s' % (name, e))
601
602 #Disable any old loggers. There's no point deleting
603 #them as other threads may continue to hold references
604 #and by disabling them, you stop them doing any logging.
605 #However, don't disable children of named loggers, as that's
606 #probably not what was intended by the user.
607 for log in existing:
608 logger = root.manager.loggerDict[log]
609 if log in child_loggers:
610 logger.level = logging.NOTSET
611 logger.handlers = []
612 logger.propagate = True
613 elif disable_existing:
614 logger.disabled = True
615
616 # And finally, do the root logger
617 root = config.get('root', None)
618 if root:
619 try:
620 self.configure_root(root)
621 except StandardError, e:
622 raise ValueError('Unable to configure root '
623 'logger: %s' % e)
624 finally:
625 logging._releaseLock()
626
627 def configure_formatter(self, config):
628 """Configure a formatter from a dictionary."""
629 if '()' in config:
630 factory = config['()'] # for use in exception handler
631 try:
632 result = self.configure_custom(config)
633 except TypeError, te:
634 if "'format'" not in str(te):
635 raise
636 #Name of parameter changed from fmt to format.
637 #Retry with old name.
638 #This is so that code can be used with older Python versions
639 #(e.g. by Django)
640 config['fmt'] = config.pop('format')
641 config['()'] = factory
642 result = self.configure_custom(config)
643 else:
644 fmt = config.get('format', None)
645 dfmt = config.get('datefmt', None)
646 result = logging.Formatter(fmt, dfmt)
647 return result
648
649 def configure_filter(self, config):
650 """Configure a filter from a dictionary."""
651 if '()' in config:
652 result = self.configure_custom(config)
653 else:
654 name = config.get('name', '')
655 result = logging.Filter(name)
656 return result
657
658 def add_filters(self, filterer, filters):
659 """Add filters to a filterer from a list of names."""
660 for f in filters:
661 try:
662 filterer.addFilter(self.config['filters'][f])
663 except StandardError, e:
664 raise ValueError('Unable to add filter %r: %s' % (f, e))
665
666 def configure_handler(self, config):
667 """Configure a handler from a dictionary."""
668 formatter = config.pop('formatter', None)
669 if formatter:
670 try:
671 formatter = self.config['formatters'][formatter]
672 except StandardError, e:
673 raise ValueError('Unable to set formatter '
674 '%r: %s' % (formatter, e))
675 level = config.pop('level', None)
676 filters = config.pop('filters', None)
677 if '()' in config:
678 c = config.pop('()')
679 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
680 c = self.resolve(c)
681 factory = c
682 else:
683 klass = self.resolve(config.pop('class'))
684 #Special case for handler which refers to another handler
685 if issubclass(klass, logging.handlers.MemoryHandler) and\
686 'target' in config:
687 try:
688 config['target'] = self.config['handlers'][config['target']]
689 except StandardError, e:
690 raise ValueError('Unable to set target handler '
691 '%r: %s' % (config['target'], e))
692 factory = klass
693 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
694 try:
695 result = factory(**kwargs)
696 except TypeError, te:
697 if "'stream'" not in str(te):
698 raise
699 #The argument name changed from strm to stream
700 #Retry with old name.
701 #This is so that code can be used with older Python versions
702 #(e.g. by Django)
703 kwargs['strm'] = kwargs.pop('stream')
704 result = factory(**kwargs)
705 if formatter:
706 result.setFormatter(formatter)
707 if level is not None:
708 result.setLevel(logging._checkLevel(level))
709 if filters:
710 self.add_filters(result, filters)
711 return result
712
713 def add_handlers(self, logger, handlers):
714 """Add handlers to a logger from a list of names."""
715 for h in handlers:
716 try:
717 logger.addHandler(self.config['handlers'][h])
718 except StandardError, e:
719 raise ValueError('Unable to add handler %r: %s' % (h, e))
720
721 def common_logger_config(self, logger, config, incremental=False):
722 """
723 Perform configuration which is common to root and non-root loggers.
724 """
725 level = config.get('level', None)
726 if level is not None:
727 logger.setLevel(logging._checkLevel(level))
728 if not incremental:
729 #Remove any existing handlers
730 for h in logger.handlers[:]:
731 logger.removeHandler(h)
732 handlers = config.get('handlers', None)
733 if handlers:
734 self.add_handlers(logger, handlers)
735 filters = config.get('filters', None)
736 if filters:
737 self.add_filters(logger, filters)
738
739 def configure_logger(self, name, config, incremental=False):
740 """Configure a non-root logger from a dictionary."""
741 logger = logging.getLogger(name)
742 self.common_logger_config(logger, config, incremental)
743 propagate = config.get('propagate', None)
744 if propagate is not None:
745 logger.propagate = propagate
746
747 def configure_root(self, config, incremental=False):
748 """Configure a root logger from a dictionary."""
749 root = logging.getLogger()
750 self.common_logger_config(root, config, incremental)
751
752dictConfigClass = DictConfigurator
753
754def dictConfig(config):
755 """Configure logging using a dictionary."""
756 dictConfigClass(config).configure()
757
758
Guido van Rossum57102f82002-11-13 16:15:58 +0000759def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
760 """
761 Start up a socket server on the specified port, and listen for new
762 configurations.
763
764 These will be sent as a file suitable for processing by fileConfig().
765 Returns a Thread object on which you can call start() to start the server,
766 and which you can join() when appropriate. To stop the server, call
767 stopListening().
768 """
769 if not thread:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000770 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000771
772 class ConfigStreamHandler(StreamRequestHandler):
773 """
774 Handler for a logging configuration request.
775
776 It expects a completely new logging configuration and uses fileConfig
777 to install it.
778 """
779 def handle(self):
780 """
781 Handle a request.
782
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000783 Each request is expected to be a 4-byte length, packed using
784 struct.pack(">L", n), followed by the config file.
785 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000786 """
787 import tempfile
788 try:
789 conn = self.connection
790 chunk = conn.recv(4)
791 if len(chunk) == 4:
792 slen = struct.unpack(">L", chunk)[0]
793 chunk = self.connection.recv(slen)
794 while len(chunk) < slen:
795 chunk = chunk + conn.recv(slen - len(chunk))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000796 try:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000797 import json
798 d =json.loads(chunk)
799 assert isinstance(d, dict)
800 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000801 except:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000802 #Apply new configuration.
803
804 file = cStringIO.StringIO(chunk)
805 try:
806 fileConfig(file)
807 except (KeyboardInterrupt, SystemExit):
808 raise
809 except:
810 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000811 except socket.error, e:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000812 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000813 raise
814 else:
815 errcode = e.args[0]
816 if errcode != RESET_ERROR:
817 raise
818
819 class ConfigSocketReceiver(ThreadingTCPServer):
820 """
821 A simple TCP socket-based logging config receiver.
822 """
823
824 allow_reuse_address = 1
825
826 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000827 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000828 ThreadingTCPServer.__init__(self, (host, port), handler)
829 logging._acquireLock()
830 self.abort = 0
831 logging._releaseLock()
832 self.timeout = 1
833
834 def serve_until_stopped(self):
835 import select
836 abort = 0
837 while not abort:
838 rd, wr, ex = select.select([self.socket.fileno()],
839 [], [],
840 self.timeout)
841 if rd:
842 self.handle_request()
843 logging._acquireLock()
844 abort = self.abort
845 logging._releaseLock()
846
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000847 def serve(rcvr, hdlr, port):
848 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000849 global _listener
850 logging._acquireLock()
851 _listener = server
852 logging._releaseLock()
853 server.serve_until_stopped()
854
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000855 return threading.Thread(target=serve,
856 args=(ConfigSocketReceiver,
857 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000858
859def stopListening():
860 """
861 Stop the listening server which was created with a call to listen().
862 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000863 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000864 if _listener:
865 logging._acquireLock()
866 _listener.abort = 1
867 _listener = None
868 logging._releaseLock()