blob: 8e4fa80c6cda292a895bfe0a152a5233dbb0eaa9 [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 Sajip31e928e2010-03-22 13:02:28 +0000101def _encoded(s):
102 return s if isinstance(s, str) else s.encode('utf-8')
103
Vinay Sajip989b69a2006-01-16 21:28:37 +0000104def _create_formatters(cp):
105 """Create and return formatters"""
106 flist = cp.get("formatters", "keys")
107 if not len(flist):
108 return {}
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000109 flist = flist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000110 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000111 formatters = {}
112 for form in flist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000113 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000114 opts = cp.options(sectname)
115 if "format" in opts:
116 fs = cp.get(sectname, "format", 1)
117 else:
118 fs = None
119 if "datefmt" in opts:
120 dfs = cp.get(sectname, "datefmt", 1)
121 else:
122 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000123 c = logging.Formatter
124 if "class" in opts:
125 class_name = cp.get(sectname, "class")
126 if class_name:
127 c = _resolve(class_name)
128 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000129 formatters[form] = f
130 return formatters
131
132
133def _install_handlers(cp, formatters):
134 """Install and return handlers"""
135 hlist = cp.get("handlers", "keys")
136 if not len(hlist):
137 return {}
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000138 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000139 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000140 handlers = {}
141 fixups = [] #for inter-handler references
142 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000143 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000144 klass = cp.get(sectname, "class")
145 opts = cp.options(sectname)
146 if "formatter" in opts:
147 fmt = cp.get(sectname, "formatter")
148 else:
149 fmt = ""
Vinay Sajipbc7e34f2008-07-18 08:59:06 +0000150 try:
151 klass = eval(klass, vars(logging))
152 except (AttributeError, NameError):
153 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000154 args = cp.get(sectname, "args")
155 args = eval(args, vars(logging))
Brett Cannone6bfe802008-08-04 00:09:43 +0000156 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000157 if "level" in opts:
158 level = cp.get(sectname, "level")
159 h.setLevel(logging._levelNames[level])
160 if len(fmt):
161 h.setFormatter(formatters[fmt])
Vinay Sajip5ff71712008-06-29 21:25:28 +0000162 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000163 if "target" in opts:
164 target = cp.get(sectname,"target")
165 else:
166 target = ""
167 if len(target): #the target handler may not be loaded yet, so keep for later...
168 fixups.append((h, target))
169 handlers[hand] = h
170 #now all handlers are loaded, fixup inter-handler references...
171 for h, t in fixups:
172 h.setTarget(handlers[t])
173 return handlers
174
175
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000176def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000177 """Create and install loggers"""
178
179 # configure the root first
180 llist = cp.get("loggers", "keys")
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000181 llist = llist.split(",")
182 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000183 llist.remove("root")
184 sectname = "logger_root"
185 root = logging.root
186 log = root
187 opts = cp.options(sectname)
188 if "level" in opts:
189 level = cp.get(sectname, "level")
190 log.setLevel(logging._levelNames[level])
191 for h in root.handlers[:]:
192 root.removeHandler(h)
193 hlist = cp.get(sectname, "handlers")
194 if len(hlist):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000195 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000196 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000197 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000198 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000199
200 #and now the others...
201 #we don't want to lose the existing loggers,
202 #since other threads may have pointers to them.
203 #existing is set to contain all existing loggers,
204 #and as we go through the new configuration we
205 #remove any which are configured. At the end,
206 #what's left in existing is the set of loggers
207 #which were in the previous configuration but
208 #which are not in the new configuration.
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000209 existing = list(root.manager.loggerDict.keys())
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000210 #The list needs to be sorted so that we can
211 #avoid disabling child loggers of explicitly
212 #named loggers. With a sorted list it is easier
213 #to find the child loggers.
Vinay Sajip31e928e2010-03-22 13:02:28 +0000214 existing.sort(key=_encoded)
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000215 #We'll keep the list of existing loggers
216 #which are children of named loggers here...
217 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000218 #now set up the new ones...
219 for log in llist:
220 sectname = "logger_%s" % log
221 qn = cp.get(sectname, "qualname")
222 opts = cp.options(sectname)
223 if "propagate" in opts:
224 propagate = cp.getint(sectname, "propagate")
225 else:
226 propagate = 1
227 logger = logging.getLogger(qn)
228 if qn in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000229 i = existing.index(qn)
230 prefixed = qn + "."
231 pflen = len(prefixed)
232 num_existing = len(existing)
233 i = i + 1 # look at the entry after qn
234 while (i < num_existing) and (existing[i][:pflen] == prefixed):
235 child_loggers.append(existing[i])
236 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000237 existing.remove(qn)
238 if "level" in opts:
239 level = cp.get(sectname, "level")
240 logger.setLevel(logging._levelNames[level])
241 for h in logger.handlers[:]:
242 logger.removeHandler(h)
243 logger.propagate = propagate
244 logger.disabled = 0
245 hlist = cp.get(sectname, "handlers")
246 if len(hlist):
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000247 hlist = hlist.split(",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000248 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000249 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000250 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000251
252 #Disable any old loggers. There's no point deleting
253 #them as other threads may continue to hold references
254 #and by disabling them, you stop them doing any logging.
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000255 #However, don't disable children of named loggers, as that's
256 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000257 for log in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000258 logger = root.manager.loggerDict[log]
259 if log in child_loggers:
260 logger.level = logging.NOTSET
261 logger.handlers = []
262 logger.propagate = 1
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000263 elif disable_existing_loggers:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000264 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000265
266
Vinay Sajip28c382f2010-02-04 18:48:53 +0000267
268IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
269
270
271def valid_ident(s):
272 m = IDENTIFIER.match(s)
273 if not m:
274 raise ValueError('Not a valid Python identifier: %r' % s)
275 return True
276
277
278# The ConvertingXXX classes are wrappers around standard Python containers,
279# and they serve to convert any suitable values in the container. The
280# conversion converts base dicts, lists and tuples to their wrapped
281# equivalents, whereas strings which match a conversion format are converted
282# appropriately.
283#
284# Each wrapper should have a configurator attribute holding the actual
285# configurator to use for conversion.
286
287class ConvertingDict(dict):
288 """A converting dictionary wrapper."""
289
290 def __getitem__(self, key):
291 value = dict.__getitem__(self, key)
292 result = self.configurator.convert(value)
293 #If the converted value is different, save for next time
294 if value is not result:
295 self[key] = result
296 if type(result) in (ConvertingDict, ConvertingList,
297 ConvertingTuple):
298 result.parent = self
299 result.key = key
300 return result
301
302 def get(self, key, default=None):
303 value = dict.get(self, key, default)
304 result = self.configurator.convert(value)
305 #If the converted value is different, save for next time
306 if value is not result:
307 self[key] = result
308 if type(result) in (ConvertingDict, ConvertingList,
309 ConvertingTuple):
310 result.parent = self
311 result.key = key
312 return result
313
314 def pop(self, key, default=None):
315 value = dict.pop(self, key, default)
316 result = self.configurator.convert(value)
317 if value is not result:
318 if type(result) in (ConvertingDict, ConvertingList,
319 ConvertingTuple):
320 result.parent = self
321 result.key = key
322 return result
323
324class ConvertingList(list):
325 """A converting list wrapper."""
326 def __getitem__(self, key):
327 value = list.__getitem__(self, key)
328 result = self.configurator.convert(value)
329 #If the converted value is different, save for next time
330 if value is not result:
331 self[key] = result
332 if type(result) in (ConvertingDict, ConvertingList,
333 ConvertingTuple):
334 result.parent = self
335 result.key = key
336 return result
337
338 def pop(self, idx=-1):
339 value = list.pop(self, idx)
340 result = self.configurator.convert(value)
341 if value is not result:
342 if type(result) in (ConvertingDict, ConvertingList,
343 ConvertingTuple):
344 result.parent = self
345 return result
346
347class ConvertingTuple(tuple):
348 """A converting tuple wrapper."""
349 def __getitem__(self, key):
350 value = tuple.__getitem__(self, key)
351 result = self.configurator.convert(value)
352 if value is not result:
353 if type(result) in (ConvertingDict, ConvertingList,
354 ConvertingTuple):
355 result.parent = self
356 result.key = key
357 return result
358
359class BaseConfigurator(object):
360 """
361 The configurator base class which defines some useful defaults.
362 """
363
364 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
365
366 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
367 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
368 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
369 DIGIT_PATTERN = re.compile(r'^\d+$')
370
371 value_converters = {
372 'ext' : 'ext_convert',
373 'cfg' : 'cfg_convert',
374 }
375
376 # We might want to use a different one, e.g. importlib
377 importer = __import__
378
379 def __init__(self, config):
380 self.config = ConvertingDict(config)
381 self.config.configurator = self
382
383 def resolve(self, s):
384 """
385 Resolve strings to objects using standard import and attribute
386 syntax.
387 """
388 name = s.split('.')
389 used = name.pop(0)
Vinay Sajip3b4849a2010-06-02 10:05:31 +0000390 try:
391 found = self.importer(used)
392 for frag in name:
393 used += '.' + frag
394 try:
395 found = getattr(found, frag)
396 except AttributeError:
397 self.importer(used)
398 found = getattr(found, frag)
399 return found
400 except ImportError:
401 e, tb = sys.exc_info()[1:]
402 v = ValueError('Cannot resolve %r: %s' % (s, e))
403 v.__cause__, v.__traceback__ = e, tb
404 raise v
Vinay Sajip28c382f2010-02-04 18:48:53 +0000405
406 def ext_convert(self, value):
407 """Default converter for the ext:// protocol."""
408 return self.resolve(value)
409
410 def cfg_convert(self, value):
411 """Default converter for the cfg:// protocol."""
412 rest = value
413 m = self.WORD_PATTERN.match(rest)
414 if m is None:
415 raise ValueError("Unable to convert %r" % value)
416 else:
417 rest = rest[m.end():]
418 d = self.config[m.groups()[0]]
419 #print d, rest
420 while rest:
421 m = self.DOT_PATTERN.match(rest)
422 if m:
423 d = d[m.groups()[0]]
424 else:
425 m = self.INDEX_PATTERN.match(rest)
426 if m:
427 idx = m.groups()[0]
428 if not self.DIGIT_PATTERN.match(idx):
429 d = d[idx]
430 else:
431 try:
432 n = int(idx) # try as number first (most likely)
433 d = d[n]
434 except TypeError:
435 d = d[idx]
436 if m:
437 rest = rest[m.end():]
438 else:
439 raise ValueError('Unable to convert '
440 '%r at %r' % (value, rest))
441 #rest should be empty
442 return d
443
444 def convert(self, value):
445 """
446 Convert values to an appropriate type. dicts, lists and tuples are
447 replaced by their converting alternatives. Strings are checked to
448 see if they have a conversion format and are converted if they do.
449 """
450 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
451 value = ConvertingDict(value)
452 value.configurator = self
453 elif not isinstance(value, ConvertingList) and isinstance(value, list):
454 value = ConvertingList(value)
455 value.configurator = self
456 elif not isinstance(value, ConvertingTuple) and\
457 isinstance(value, tuple):
458 value = ConvertingTuple(value)
459 value.configurator = self
460 elif isinstance(value, basestring): # str for py3k
461 m = self.CONVERT_PATTERN.match(value)
462 if m:
463 d = m.groupdict()
464 prefix = d['prefix']
465 converter = self.value_converters.get(prefix, None)
466 if converter:
467 suffix = d['suffix']
468 converter = getattr(self, converter)
469 value = converter(suffix)
470 return value
471
472 def configure_custom(self, config):
473 """Configure an object with a user-supplied factory."""
474 c = config.pop('()')
475 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
476 c = self.resolve(c)
477 props = config.pop('.', None)
478 # Check for valid identifiers
479 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
480 result = c(**kwargs)
481 if props:
482 for name, value in props.items():
483 setattr(result, name, value)
484 return result
485
Vinay Sajip1adbee22010-03-06 15:56:03 +0000486 def as_tuple(self, value):
487 """Utility function which converts lists to tuples."""
488 if isinstance(value, list):
489 value = tuple(value)
490 return value
491
Vinay Sajip28c382f2010-02-04 18:48:53 +0000492class DictConfigurator(BaseConfigurator):
493 """
494 Configure logging using a dictionary-like object to describe the
495 configuration.
496 """
497
498 def configure(self):
499 """Do the configuration."""
500
501 config = self.config
Vinay Sajipd45a2782010-03-06 15:12:08 +0000502 if 'version' not in config:
503 raise ValueError("dictionary doesn't specify a version")
504 if config['version'] != 1:
505 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000506 incremental = config.pop('incremental', False)
507 EMPTY_DICT = {}
508 logging._acquireLock()
509 try:
510 if incremental:
511 handlers = config.get('handlers', EMPTY_DICT)
512 for name in handlers:
513 if name not in logging._handlers:
514 raise ValueError('No handler found with '
515 'name %r' % name)
516 else:
517 try:
518 handler = logging._handlers[name]
519 handler_config = handlers[name]
520 level = handler_config.get('level', None)
521 if level:
522 handler.setLevel(logging._checkLevel(level))
523 except StandardError, e:
524 raise ValueError('Unable to configure handler '
525 '%r: %s' % (name, e))
526 loggers = config.get('loggers', EMPTY_DICT)
527 for name in loggers:
528 try:
529 self.configure_logger(name, loggers[name], True)
530 except StandardError, e:
531 raise ValueError('Unable to configure logger '
532 '%r: %s' % (name, e))
533 root = config.get('root', None)
534 if root:
535 try:
536 self.configure_root(root, True)
537 except StandardError, e:
538 raise ValueError('Unable to configure root '
539 'logger: %s' % e)
540 else:
541 disable_existing = config.pop('disable_existing_loggers', True)
542
543 logging._handlers.clear()
544 del logging._handlerList[:]
545
546 # Do formatters first - they don't refer to anything else
547 formatters = config.get('formatters', EMPTY_DICT)
548 for name in formatters:
549 try:
550 formatters[name] = self.configure_formatter(
551 formatters[name])
552 except StandardError, e:
553 raise ValueError('Unable to configure '
554 'formatter %r: %s' % (name, e))
555 # Next, do filters - they don't refer to anything else, either
556 filters = config.get('filters', EMPTY_DICT)
557 for name in filters:
558 try:
559 filters[name] = self.configure_filter(filters[name])
560 except StandardError, e:
561 raise ValueError('Unable to configure '
562 'filter %r: %s' % (name, e))
563
564 # Next, do handlers - they refer to formatters and filters
565 # As handlers can refer to other handlers, sort the keys
566 # to allow a deterministic order of configuration
567 handlers = config.get('handlers', EMPTY_DICT)
568 for name in sorted(handlers):
569 try:
570 handler = self.configure_handler(handlers[name])
571 handler.name = name
572 handlers[name] = handler
573 except StandardError, e:
574 raise ValueError('Unable to configure handler '
575 '%r: %s' % (name, e))
576 # Next, do loggers - they refer to handlers and filters
577
578 #we don't want to lose the existing loggers,
579 #since other threads may have pointers to them.
580 #existing is set to contain all existing loggers,
581 #and as we go through the new configuration we
582 #remove any which are configured. At the end,
583 #what's left in existing is the set of loggers
584 #which were in the previous configuration but
585 #which are not in the new configuration.
586 root = logging.root
587 existing = root.manager.loggerDict.keys()
588 #The list needs to be sorted so that we can
589 #avoid disabling child loggers of explicitly
590 #named loggers. With a sorted list it is easier
591 #to find the child loggers.
Vinay Sajip31e928e2010-03-22 13:02:28 +0000592 existing.sort(key=_encoded)
Vinay Sajip28c382f2010-02-04 18:48:53 +0000593 #We'll keep the list of existing loggers
594 #which are children of named loggers here...
595 child_loggers = []
596 #now set up the new ones...
597 loggers = config.get('loggers', EMPTY_DICT)
598 for name in loggers:
599 if name in existing:
600 i = existing.index(name)
601 prefixed = name + "."
602 pflen = len(prefixed)
603 num_existing = len(existing)
604 i = i + 1 # look at the entry after name
605 while (i < num_existing) and\
606 (existing[i][:pflen] == prefixed):
607 child_loggers.append(existing[i])
608 i = i + 1
609 existing.remove(name)
610 try:
611 self.configure_logger(name, loggers[name])
612 except StandardError, e:
613 raise ValueError('Unable to configure logger '
614 '%r: %s' % (name, e))
615
616 #Disable any old loggers. There's no point deleting
617 #them as other threads may continue to hold references
618 #and by disabling them, you stop them doing any logging.
619 #However, don't disable children of named loggers, as that's
620 #probably not what was intended by the user.
621 for log in existing:
622 logger = root.manager.loggerDict[log]
623 if log in child_loggers:
624 logger.level = logging.NOTSET
625 logger.handlers = []
626 logger.propagate = True
627 elif disable_existing:
628 logger.disabled = True
629
630 # And finally, do the root logger
631 root = config.get('root', None)
632 if root:
633 try:
634 self.configure_root(root)
635 except StandardError, e:
636 raise ValueError('Unable to configure root '
637 'logger: %s' % e)
638 finally:
639 logging._releaseLock()
640
641 def configure_formatter(self, config):
642 """Configure a formatter from a dictionary."""
643 if '()' in config:
644 factory = config['()'] # for use in exception handler
645 try:
646 result = self.configure_custom(config)
647 except TypeError, te:
648 if "'format'" not in str(te):
649 raise
650 #Name of parameter changed from fmt to format.
651 #Retry with old name.
652 #This is so that code can be used with older Python versions
653 #(e.g. by Django)
654 config['fmt'] = config.pop('format')
655 config['()'] = factory
656 result = self.configure_custom(config)
657 else:
658 fmt = config.get('format', None)
659 dfmt = config.get('datefmt', None)
660 result = logging.Formatter(fmt, dfmt)
661 return result
662
663 def configure_filter(self, config):
664 """Configure a filter from a dictionary."""
665 if '()' in config:
666 result = self.configure_custom(config)
667 else:
668 name = config.get('name', '')
669 result = logging.Filter(name)
670 return result
671
672 def add_filters(self, filterer, filters):
673 """Add filters to a filterer from a list of names."""
674 for f in filters:
675 try:
676 filterer.addFilter(self.config['filters'][f])
677 except StandardError, e:
678 raise ValueError('Unable to add filter %r: %s' % (f, e))
679
680 def configure_handler(self, config):
681 """Configure a handler from a dictionary."""
682 formatter = config.pop('formatter', None)
683 if formatter:
684 try:
685 formatter = self.config['formatters'][formatter]
686 except StandardError, e:
687 raise ValueError('Unable to set formatter '
688 '%r: %s' % (formatter, e))
689 level = config.pop('level', None)
690 filters = config.pop('filters', None)
691 if '()' in config:
692 c = config.pop('()')
693 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
694 c = self.resolve(c)
695 factory = c
696 else:
697 klass = self.resolve(config.pop('class'))
698 #Special case for handler which refers to another handler
699 if issubclass(klass, logging.handlers.MemoryHandler) and\
700 'target' in config:
701 try:
702 config['target'] = self.config['handlers'][config['target']]
703 except StandardError, e:
704 raise ValueError('Unable to set target handler '
705 '%r: %s' % (config['target'], e))
Vinay Sajip1adbee22010-03-06 15:56:03 +0000706 elif issubclass(klass, logging.handlers.SMTPHandler) and\
707 'mailhost' in config:
708 config['mailhost'] = self.as_tuple(config['mailhost'])
709 elif issubclass(klass, logging.handlers.SysLogHandler) and\
710 'address' in config:
711 config['address'] = self.as_tuple(config['address'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000712 factory = klass
713 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
714 try:
715 result = factory(**kwargs)
716 except TypeError, te:
717 if "'stream'" not in str(te):
718 raise
719 #The argument name changed from strm to stream
720 #Retry with old name.
721 #This is so that code can be used with older Python versions
722 #(e.g. by Django)
723 kwargs['strm'] = kwargs.pop('stream')
724 result = factory(**kwargs)
725 if formatter:
726 result.setFormatter(formatter)
727 if level is not None:
728 result.setLevel(logging._checkLevel(level))
729 if filters:
730 self.add_filters(result, filters)
731 return result
732
733 def add_handlers(self, logger, handlers):
734 """Add handlers to a logger from a list of names."""
735 for h in handlers:
736 try:
737 logger.addHandler(self.config['handlers'][h])
738 except StandardError, e:
739 raise ValueError('Unable to add handler %r: %s' % (h, e))
740
741 def common_logger_config(self, logger, config, incremental=False):
742 """
743 Perform configuration which is common to root and non-root loggers.
744 """
745 level = config.get('level', None)
746 if level is not None:
747 logger.setLevel(logging._checkLevel(level))
748 if not incremental:
749 #Remove any existing handlers
750 for h in logger.handlers[:]:
751 logger.removeHandler(h)
752 handlers = config.get('handlers', None)
753 if handlers:
754 self.add_handlers(logger, handlers)
755 filters = config.get('filters', None)
756 if filters:
757 self.add_filters(logger, filters)
758
759 def configure_logger(self, name, config, incremental=False):
760 """Configure a non-root logger from a dictionary."""
761 logger = logging.getLogger(name)
762 self.common_logger_config(logger, config, incremental)
763 propagate = config.get('propagate', None)
764 if propagate is not None:
765 logger.propagate = propagate
766
767 def configure_root(self, config, incremental=False):
768 """Configure a root logger from a dictionary."""
769 root = logging.getLogger()
770 self.common_logger_config(root, config, incremental)
771
772dictConfigClass = DictConfigurator
773
774def dictConfig(config):
775 """Configure logging using a dictionary."""
776 dictConfigClass(config).configure()
777
778
Guido van Rossum57102f82002-11-13 16:15:58 +0000779def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
780 """
781 Start up a socket server on the specified port, and listen for new
782 configurations.
783
784 These will be sent as a file suitable for processing by fileConfig().
785 Returns a Thread object on which you can call start() to start the server,
786 and which you can join() when appropriate. To stop the server, call
787 stopListening().
788 """
789 if not thread:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000790 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000791
792 class ConfigStreamHandler(StreamRequestHandler):
793 """
794 Handler for a logging configuration request.
795
796 It expects a completely new logging configuration and uses fileConfig
797 to install it.
798 """
799 def handle(self):
800 """
801 Handle a request.
802
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000803 Each request is expected to be a 4-byte length, packed using
804 struct.pack(">L", n), followed by the config file.
805 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000806 """
807 import tempfile
808 try:
809 conn = self.connection
810 chunk = conn.recv(4)
811 if len(chunk) == 4:
812 slen = struct.unpack(">L", chunk)[0]
813 chunk = self.connection.recv(slen)
814 while len(chunk) < slen:
815 chunk = chunk + conn.recv(slen - len(chunk))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000816 try:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000817 import json
818 d =json.loads(chunk)
819 assert isinstance(d, dict)
820 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000821 except:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000822 #Apply new configuration.
823
824 file = cStringIO.StringIO(chunk)
825 try:
826 fileConfig(file)
827 except (KeyboardInterrupt, SystemExit):
828 raise
829 except:
830 traceback.print_exc()
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000831 if self.server.ready:
832 self.server.ready.set()
Guido van Rossum57102f82002-11-13 16:15:58 +0000833 except socket.error, e:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000834 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000835 raise
836 else:
837 errcode = e.args[0]
838 if errcode != RESET_ERROR:
839 raise
840
841 class ConfigSocketReceiver(ThreadingTCPServer):
842 """
843 A simple TCP socket-based logging config receiver.
844 """
845
846 allow_reuse_address = 1
847
848 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000849 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000850 ThreadingTCPServer.__init__(self, (host, port), handler)
851 logging._acquireLock()
852 self.abort = 0
853 logging._releaseLock()
854 self.timeout = 1
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000855 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000856
857 def serve_until_stopped(self):
858 import select
859 abort = 0
860 while not abort:
861 rd, wr, ex = select.select([self.socket.fileno()],
862 [], [],
863 self.timeout)
864 if rd:
865 self.handle_request()
866 logging._acquireLock()
867 abort = self.abort
868 logging._releaseLock()
Brian Curtine27731d2010-10-31 04:44:04 +0000869 self.socket.close()
Guido van Rossum57102f82002-11-13 16:15:58 +0000870
Benjamin Peterson239f1382010-02-06 22:08:15 +0000871 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000872
Benjamin Peterson239f1382010-02-06 22:08:15 +0000873 def __init__(self, rcvr, hdlr, port):
874 super(Server, self).__init__()
875 self.rcvr = rcvr
876 self.hdlr = hdlr
877 self.port = port
878 self.ready = threading.Event()
879
880 def run(self):
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000881 server = self.rcvr(port=self.port, handler=self.hdlr,
882 ready=self.ready)
Vinay Sajip27a13702010-05-03 15:11:53 +0000883 if self.port == 0:
884 self.port = server.server_address[1]
Benjamin Peterson239f1382010-02-06 22:08:15 +0000885 self.ready.set()
886 global _listener
887 logging._acquireLock()
888 _listener = server
889 logging._releaseLock()
890 server.serve_until_stopped()
891
892 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000893
894def stopListening():
895 """
896 Stop the listening server which was created with a call to listen().
897 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000898 global _listener
Vinay Sajip3eac5912010-09-25 17:48:25 +0000899 logging._acquireLock()
900 try:
901 if _listener:
902 _listener.abort = 1
903 _listener = None
904 finally:
Guido van Rossum57102f82002-11-13 16:15:58 +0000905 logging._releaseLock()