blob: 21e10c14f574ef2bd8cb6392d66ed06206bbbd30 [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
Vinay Sajip1adbee22010-03-06 15:56:03 +0000477 def as_tuple(self, value):
478 """Utility function which converts lists to tuples."""
479 if isinstance(value, list):
480 value = tuple(value)
481 return value
482
Vinay Sajip28c382f2010-02-04 18:48:53 +0000483class DictConfigurator(BaseConfigurator):
484 """
485 Configure logging using a dictionary-like object to describe the
486 configuration.
487 """
488
489 def configure(self):
490 """Do the configuration."""
491
492 config = self.config
Vinay Sajipd45a2782010-03-06 15:12:08 +0000493 if 'version' not in config:
494 raise ValueError("dictionary doesn't specify a version")
495 if config['version'] != 1:
496 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000497 incremental = config.pop('incremental', False)
498 EMPTY_DICT = {}
499 logging._acquireLock()
500 try:
501 if incremental:
502 handlers = config.get('handlers', EMPTY_DICT)
503 for name in handlers:
504 if name not in logging._handlers:
505 raise ValueError('No handler found with '
506 'name %r' % name)
507 else:
508 try:
509 handler = logging._handlers[name]
510 handler_config = handlers[name]
511 level = handler_config.get('level', None)
512 if level:
513 handler.setLevel(logging._checkLevel(level))
514 except StandardError, e:
515 raise ValueError('Unable to configure handler '
516 '%r: %s' % (name, e))
517 loggers = config.get('loggers', EMPTY_DICT)
518 for name in loggers:
519 try:
520 self.configure_logger(name, loggers[name], True)
521 except StandardError, e:
522 raise ValueError('Unable to configure logger '
523 '%r: %s' % (name, e))
524 root = config.get('root', None)
525 if root:
526 try:
527 self.configure_root(root, True)
528 except StandardError, e:
529 raise ValueError('Unable to configure root '
530 'logger: %s' % e)
531 else:
532 disable_existing = config.pop('disable_existing_loggers', True)
533
534 logging._handlers.clear()
535 del logging._handlerList[:]
536
537 # Do formatters first - they don't refer to anything else
538 formatters = config.get('formatters', EMPTY_DICT)
539 for name in formatters:
540 try:
541 formatters[name] = self.configure_formatter(
542 formatters[name])
543 except StandardError, e:
544 raise ValueError('Unable to configure '
545 'formatter %r: %s' % (name, e))
546 # Next, do filters - they don't refer to anything else, either
547 filters = config.get('filters', EMPTY_DICT)
548 for name in filters:
549 try:
550 filters[name] = self.configure_filter(filters[name])
551 except StandardError, e:
552 raise ValueError('Unable to configure '
553 'filter %r: %s' % (name, e))
554
555 # Next, do handlers - they refer to formatters and filters
556 # As handlers can refer to other handlers, sort the keys
557 # to allow a deterministic order of configuration
558 handlers = config.get('handlers', EMPTY_DICT)
559 for name in sorted(handlers):
560 try:
561 handler = self.configure_handler(handlers[name])
562 handler.name = name
563 handlers[name] = handler
564 except StandardError, e:
565 raise ValueError('Unable to configure handler '
566 '%r: %s' % (name, e))
567 # Next, do loggers - they refer to handlers and filters
568
569 #we don't want to lose the existing loggers,
570 #since other threads may have pointers to them.
571 #existing is set to contain all existing loggers,
572 #and as we go through the new configuration we
573 #remove any which are configured. At the end,
574 #what's left in existing is the set of loggers
575 #which were in the previous configuration but
576 #which are not in the new configuration.
577 root = logging.root
578 existing = root.manager.loggerDict.keys()
579 #The list needs to be sorted so that we can
580 #avoid disabling child loggers of explicitly
581 #named loggers. With a sorted list it is easier
582 #to find the child loggers.
583 existing.sort()
584 #We'll keep the list of existing loggers
585 #which are children of named loggers here...
586 child_loggers = []
587 #now set up the new ones...
588 loggers = config.get('loggers', EMPTY_DICT)
589 for name in loggers:
590 if name in existing:
591 i = existing.index(name)
592 prefixed = name + "."
593 pflen = len(prefixed)
594 num_existing = len(existing)
595 i = i + 1 # look at the entry after name
596 while (i < num_existing) and\
597 (existing[i][:pflen] == prefixed):
598 child_loggers.append(existing[i])
599 i = i + 1
600 existing.remove(name)
601 try:
602 self.configure_logger(name, loggers[name])
603 except StandardError, e:
604 raise ValueError('Unable to configure logger '
605 '%r: %s' % (name, e))
606
607 #Disable any old loggers. There's no point deleting
608 #them as other threads may continue to hold references
609 #and by disabling them, you stop them doing any logging.
610 #However, don't disable children of named loggers, as that's
611 #probably not what was intended by the user.
612 for log in existing:
613 logger = root.manager.loggerDict[log]
614 if log in child_loggers:
615 logger.level = logging.NOTSET
616 logger.handlers = []
617 logger.propagate = True
618 elif disable_existing:
619 logger.disabled = True
620
621 # And finally, do the root logger
622 root = config.get('root', None)
623 if root:
624 try:
625 self.configure_root(root)
626 except StandardError, e:
627 raise ValueError('Unable to configure root '
628 'logger: %s' % e)
629 finally:
630 logging._releaseLock()
631
632 def configure_formatter(self, config):
633 """Configure a formatter from a dictionary."""
634 if '()' in config:
635 factory = config['()'] # for use in exception handler
636 try:
637 result = self.configure_custom(config)
638 except TypeError, te:
639 if "'format'" not in str(te):
640 raise
641 #Name of parameter changed from fmt to format.
642 #Retry with old name.
643 #This is so that code can be used with older Python versions
644 #(e.g. by Django)
645 config['fmt'] = config.pop('format')
646 config['()'] = factory
647 result = self.configure_custom(config)
648 else:
649 fmt = config.get('format', None)
650 dfmt = config.get('datefmt', None)
651 result = logging.Formatter(fmt, dfmt)
652 return result
653
654 def configure_filter(self, config):
655 """Configure a filter from a dictionary."""
656 if '()' in config:
657 result = self.configure_custom(config)
658 else:
659 name = config.get('name', '')
660 result = logging.Filter(name)
661 return result
662
663 def add_filters(self, filterer, filters):
664 """Add filters to a filterer from a list of names."""
665 for f in filters:
666 try:
667 filterer.addFilter(self.config['filters'][f])
668 except StandardError, e:
669 raise ValueError('Unable to add filter %r: %s' % (f, e))
670
671 def configure_handler(self, config):
672 """Configure a handler from a dictionary."""
673 formatter = config.pop('formatter', None)
674 if formatter:
675 try:
676 formatter = self.config['formatters'][formatter]
677 except StandardError, e:
678 raise ValueError('Unable to set formatter '
679 '%r: %s' % (formatter, e))
680 level = config.pop('level', None)
681 filters = config.pop('filters', None)
682 if '()' in config:
683 c = config.pop('()')
684 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
685 c = self.resolve(c)
686 factory = c
687 else:
688 klass = self.resolve(config.pop('class'))
689 #Special case for handler which refers to another handler
690 if issubclass(klass, logging.handlers.MemoryHandler) and\
691 'target' in config:
692 try:
693 config['target'] = self.config['handlers'][config['target']]
694 except StandardError, e:
695 raise ValueError('Unable to set target handler '
696 '%r: %s' % (config['target'], e))
Vinay Sajip1adbee22010-03-06 15:56:03 +0000697 elif issubclass(klass, logging.handlers.SMTPHandler) and\
698 'mailhost' in config:
699 config['mailhost'] = self.as_tuple(config['mailhost'])
700 elif issubclass(klass, logging.handlers.SysLogHandler) and\
701 'address' in config:
702 config['address'] = self.as_tuple(config['address'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000703 factory = klass
704 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
705 try:
706 result = factory(**kwargs)
707 except TypeError, te:
708 if "'stream'" not in str(te):
709 raise
710 #The argument name changed from strm to stream
711 #Retry with old name.
712 #This is so that code can be used with older Python versions
713 #(e.g. by Django)
714 kwargs['strm'] = kwargs.pop('stream')
715 result = factory(**kwargs)
716 if formatter:
717 result.setFormatter(formatter)
718 if level is not None:
719 result.setLevel(logging._checkLevel(level))
720 if filters:
721 self.add_filters(result, filters)
722 return result
723
724 def add_handlers(self, logger, handlers):
725 """Add handlers to a logger from a list of names."""
726 for h in handlers:
727 try:
728 logger.addHandler(self.config['handlers'][h])
729 except StandardError, e:
730 raise ValueError('Unable to add handler %r: %s' % (h, e))
731
732 def common_logger_config(self, logger, config, incremental=False):
733 """
734 Perform configuration which is common to root and non-root loggers.
735 """
736 level = config.get('level', None)
737 if level is not None:
738 logger.setLevel(logging._checkLevel(level))
739 if not incremental:
740 #Remove any existing handlers
741 for h in logger.handlers[:]:
742 logger.removeHandler(h)
743 handlers = config.get('handlers', None)
744 if handlers:
745 self.add_handlers(logger, handlers)
746 filters = config.get('filters', None)
747 if filters:
748 self.add_filters(logger, filters)
749
750 def configure_logger(self, name, config, incremental=False):
751 """Configure a non-root logger from a dictionary."""
752 logger = logging.getLogger(name)
753 self.common_logger_config(logger, config, incremental)
754 propagate = config.get('propagate', None)
755 if propagate is not None:
756 logger.propagate = propagate
757
758 def configure_root(self, config, incremental=False):
759 """Configure a root logger from a dictionary."""
760 root = logging.getLogger()
761 self.common_logger_config(root, config, incremental)
762
763dictConfigClass = DictConfigurator
764
765def dictConfig(config):
766 """Configure logging using a dictionary."""
767 dictConfigClass(config).configure()
768
769
Guido van Rossum57102f82002-11-13 16:15:58 +0000770def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
771 """
772 Start up a socket server on the specified port, and listen for new
773 configurations.
774
775 These will be sent as a file suitable for processing by fileConfig().
776 Returns a Thread object on which you can call start() to start the server,
777 and which you can join() when appropriate. To stop the server, call
778 stopListening().
779 """
780 if not thread:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000781 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000782
783 class ConfigStreamHandler(StreamRequestHandler):
784 """
785 Handler for a logging configuration request.
786
787 It expects a completely new logging configuration and uses fileConfig
788 to install it.
789 """
790 def handle(self):
791 """
792 Handle a request.
793
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000794 Each request is expected to be a 4-byte length, packed using
795 struct.pack(">L", n), followed by the config file.
796 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000797 """
798 import tempfile
799 try:
800 conn = self.connection
801 chunk = conn.recv(4)
802 if len(chunk) == 4:
803 slen = struct.unpack(">L", chunk)[0]
804 chunk = self.connection.recv(slen)
805 while len(chunk) < slen:
806 chunk = chunk + conn.recv(slen - len(chunk))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000807 try:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000808 import json
809 d =json.loads(chunk)
810 assert isinstance(d, dict)
811 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000812 except:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000813 #Apply new configuration.
814
815 file = cStringIO.StringIO(chunk)
816 try:
817 fileConfig(file)
818 except (KeyboardInterrupt, SystemExit):
819 raise
820 except:
821 traceback.print_exc()
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000822 if self.server.ready:
823 self.server.ready.set()
Guido van Rossum57102f82002-11-13 16:15:58 +0000824 except socket.error, e:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000825 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000826 raise
827 else:
828 errcode = e.args[0]
829 if errcode != RESET_ERROR:
830 raise
831
832 class ConfigSocketReceiver(ThreadingTCPServer):
833 """
834 A simple TCP socket-based logging config receiver.
835 """
836
837 allow_reuse_address = 1
838
839 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000840 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000841 ThreadingTCPServer.__init__(self, (host, port), handler)
842 logging._acquireLock()
843 self.abort = 0
844 logging._releaseLock()
845 self.timeout = 1
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000846 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000847
848 def serve_until_stopped(self):
849 import select
850 abort = 0
851 while not abort:
852 rd, wr, ex = select.select([self.socket.fileno()],
853 [], [],
854 self.timeout)
855 if rd:
856 self.handle_request()
857 logging._acquireLock()
858 abort = self.abort
859 logging._releaseLock()
860
Benjamin Peterson239f1382010-02-06 22:08:15 +0000861 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000862
Benjamin Peterson239f1382010-02-06 22:08:15 +0000863 def __init__(self, rcvr, hdlr, port):
864 super(Server, self).__init__()
865 self.rcvr = rcvr
866 self.hdlr = hdlr
867 self.port = port
868 self.ready = threading.Event()
869
870 def run(self):
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000871 server = self.rcvr(port=self.port, handler=self.hdlr,
872 ready=self.ready)
Benjamin Peterson239f1382010-02-06 22:08:15 +0000873 self.ready.set()
874 global _listener
875 logging._acquireLock()
876 _listener = server
877 logging._releaseLock()
878 server.serve_until_stopped()
879
880 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000881
882def stopListening():
883 """
884 Stop the listening server which was created with a call to listen().
885 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000886 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000887 if _listener:
888 logging._acquireLock()
889 _listener.abort = 1
890 _listener = None
891 logging._releaseLock()