blob: 07b9d1a8342ac6b56d53daa48033dd34c87e5f18 [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)
390 found = self.importer(used)
391 for frag in name:
392 used += '.' + frag
393 try:
394 found = getattr(found, frag)
395 except AttributeError:
396 self.importer(used)
397 found = getattr(found, frag)
398 return found
399
400 def ext_convert(self, value):
401 """Default converter for the ext:// protocol."""
402 return self.resolve(value)
403
404 def cfg_convert(self, value):
405 """Default converter for the cfg:// protocol."""
406 rest = value
407 m = self.WORD_PATTERN.match(rest)
408 if m is None:
409 raise ValueError("Unable to convert %r" % value)
410 else:
411 rest = rest[m.end():]
412 d = self.config[m.groups()[0]]
413 #print d, rest
414 while rest:
415 m = self.DOT_PATTERN.match(rest)
416 if m:
417 d = d[m.groups()[0]]
418 else:
419 m = self.INDEX_PATTERN.match(rest)
420 if m:
421 idx = m.groups()[0]
422 if not self.DIGIT_PATTERN.match(idx):
423 d = d[idx]
424 else:
425 try:
426 n = int(idx) # try as number first (most likely)
427 d = d[n]
428 except TypeError:
429 d = d[idx]
430 if m:
431 rest = rest[m.end():]
432 else:
433 raise ValueError('Unable to convert '
434 '%r at %r' % (value, rest))
435 #rest should be empty
436 return d
437
438 def convert(self, value):
439 """
440 Convert values to an appropriate type. dicts, lists and tuples are
441 replaced by their converting alternatives. Strings are checked to
442 see if they have a conversion format and are converted if they do.
443 """
444 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
445 value = ConvertingDict(value)
446 value.configurator = self
447 elif not isinstance(value, ConvertingList) and isinstance(value, list):
448 value = ConvertingList(value)
449 value.configurator = self
450 elif not isinstance(value, ConvertingTuple) and\
451 isinstance(value, tuple):
452 value = ConvertingTuple(value)
453 value.configurator = self
454 elif isinstance(value, basestring): # str for py3k
455 m = self.CONVERT_PATTERN.match(value)
456 if m:
457 d = m.groupdict()
458 prefix = d['prefix']
459 converter = self.value_converters.get(prefix, None)
460 if converter:
461 suffix = d['suffix']
462 converter = getattr(self, converter)
463 value = converter(suffix)
464 return value
465
466 def configure_custom(self, config):
467 """Configure an object with a user-supplied factory."""
468 c = config.pop('()')
469 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
470 c = self.resolve(c)
471 props = config.pop('.', None)
472 # Check for valid identifiers
473 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
474 result = c(**kwargs)
475 if props:
476 for name, value in props.items():
477 setattr(result, name, value)
478 return result
479
Vinay Sajip1adbee22010-03-06 15:56:03 +0000480 def as_tuple(self, value):
481 """Utility function which converts lists to tuples."""
482 if isinstance(value, list):
483 value = tuple(value)
484 return value
485
Vinay Sajip28c382f2010-02-04 18:48:53 +0000486class DictConfigurator(BaseConfigurator):
487 """
488 Configure logging using a dictionary-like object to describe the
489 configuration.
490 """
491
492 def configure(self):
493 """Do the configuration."""
494
495 config = self.config
Vinay Sajipd45a2782010-03-06 15:12:08 +0000496 if 'version' not in config:
497 raise ValueError("dictionary doesn't specify a version")
498 if config['version'] != 1:
499 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000500 incremental = config.pop('incremental', False)
501 EMPTY_DICT = {}
502 logging._acquireLock()
503 try:
504 if incremental:
505 handlers = config.get('handlers', EMPTY_DICT)
506 for name in handlers:
507 if name not in logging._handlers:
508 raise ValueError('No handler found with '
509 'name %r' % name)
510 else:
511 try:
512 handler = logging._handlers[name]
513 handler_config = handlers[name]
514 level = handler_config.get('level', None)
515 if level:
516 handler.setLevel(logging._checkLevel(level))
517 except StandardError, e:
518 raise ValueError('Unable to configure handler '
519 '%r: %s' % (name, e))
520 loggers = config.get('loggers', EMPTY_DICT)
521 for name in loggers:
522 try:
523 self.configure_logger(name, loggers[name], True)
524 except StandardError, e:
525 raise ValueError('Unable to configure logger '
526 '%r: %s' % (name, e))
527 root = config.get('root', None)
528 if root:
529 try:
530 self.configure_root(root, True)
531 except StandardError, e:
532 raise ValueError('Unable to configure root '
533 'logger: %s' % e)
534 else:
535 disable_existing = config.pop('disable_existing_loggers', True)
536
537 logging._handlers.clear()
538 del logging._handlerList[:]
539
540 # Do formatters first - they don't refer to anything else
541 formatters = config.get('formatters', EMPTY_DICT)
542 for name in formatters:
543 try:
544 formatters[name] = self.configure_formatter(
545 formatters[name])
546 except StandardError, e:
547 raise ValueError('Unable to configure '
548 'formatter %r: %s' % (name, e))
549 # Next, do filters - they don't refer to anything else, either
550 filters = config.get('filters', EMPTY_DICT)
551 for name in filters:
552 try:
553 filters[name] = self.configure_filter(filters[name])
554 except StandardError, e:
555 raise ValueError('Unable to configure '
556 'filter %r: %s' % (name, e))
557
558 # Next, do handlers - they refer to formatters and filters
559 # As handlers can refer to other handlers, sort the keys
560 # to allow a deterministic order of configuration
561 handlers = config.get('handlers', EMPTY_DICT)
562 for name in sorted(handlers):
563 try:
564 handler = self.configure_handler(handlers[name])
565 handler.name = name
566 handlers[name] = handler
567 except StandardError, e:
568 raise ValueError('Unable to configure handler '
569 '%r: %s' % (name, e))
570 # Next, do loggers - they refer to handlers and filters
571
572 #we don't want to lose the existing loggers,
573 #since other threads may have pointers to them.
574 #existing is set to contain all existing loggers,
575 #and as we go through the new configuration we
576 #remove any which are configured. At the end,
577 #what's left in existing is the set of loggers
578 #which were in the previous configuration but
579 #which are not in the new configuration.
580 root = logging.root
581 existing = root.manager.loggerDict.keys()
582 #The list needs to be sorted so that we can
583 #avoid disabling child loggers of explicitly
584 #named loggers. With a sorted list it is easier
585 #to find the child loggers.
Vinay Sajip31e928e2010-03-22 13:02:28 +0000586 existing.sort(key=_encoded)
Vinay Sajip28c382f2010-02-04 18:48:53 +0000587 #We'll keep the list of existing loggers
588 #which are children of named loggers here...
589 child_loggers = []
590 #now set up the new ones...
591 loggers = config.get('loggers', EMPTY_DICT)
592 for name in loggers:
593 if name in existing:
594 i = existing.index(name)
595 prefixed = name + "."
596 pflen = len(prefixed)
597 num_existing = len(existing)
598 i = i + 1 # look at the entry after name
599 while (i < num_existing) and\
600 (existing[i][:pflen] == prefixed):
601 child_loggers.append(existing[i])
602 i = i + 1
603 existing.remove(name)
604 try:
605 self.configure_logger(name, loggers[name])
606 except StandardError, e:
607 raise ValueError('Unable to configure logger '
608 '%r: %s' % (name, e))
609
610 #Disable any old loggers. There's no point deleting
611 #them as other threads may continue to hold references
612 #and by disabling them, you stop them doing any logging.
613 #However, don't disable children of named loggers, as that's
614 #probably not what was intended by the user.
615 for log in existing:
616 logger = root.manager.loggerDict[log]
617 if log in child_loggers:
618 logger.level = logging.NOTSET
619 logger.handlers = []
620 logger.propagate = True
621 elif disable_existing:
622 logger.disabled = True
623
624 # And finally, do the root logger
625 root = config.get('root', None)
626 if root:
627 try:
628 self.configure_root(root)
629 except StandardError, e:
630 raise ValueError('Unable to configure root '
631 'logger: %s' % e)
632 finally:
633 logging._releaseLock()
634
635 def configure_formatter(self, config):
636 """Configure a formatter from a dictionary."""
637 if '()' in config:
638 factory = config['()'] # for use in exception handler
639 try:
640 result = self.configure_custom(config)
641 except TypeError, te:
642 if "'format'" not in str(te):
643 raise
644 #Name of parameter changed from fmt to format.
645 #Retry with old name.
646 #This is so that code can be used with older Python versions
647 #(e.g. by Django)
648 config['fmt'] = config.pop('format')
649 config['()'] = factory
650 result = self.configure_custom(config)
651 else:
652 fmt = config.get('format', None)
653 dfmt = config.get('datefmt', None)
654 result = logging.Formatter(fmt, dfmt)
655 return result
656
657 def configure_filter(self, config):
658 """Configure a filter from a dictionary."""
659 if '()' in config:
660 result = self.configure_custom(config)
661 else:
662 name = config.get('name', '')
663 result = logging.Filter(name)
664 return result
665
666 def add_filters(self, filterer, filters):
667 """Add filters to a filterer from a list of names."""
668 for f in filters:
669 try:
670 filterer.addFilter(self.config['filters'][f])
671 except StandardError, e:
672 raise ValueError('Unable to add filter %r: %s' % (f, e))
673
674 def configure_handler(self, config):
675 """Configure a handler from a dictionary."""
676 formatter = config.pop('formatter', None)
677 if formatter:
678 try:
679 formatter = self.config['formatters'][formatter]
680 except StandardError, e:
681 raise ValueError('Unable to set formatter '
682 '%r: %s' % (formatter, e))
683 level = config.pop('level', None)
684 filters = config.pop('filters', None)
685 if '()' in config:
686 c = config.pop('()')
687 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
688 c = self.resolve(c)
689 factory = c
690 else:
691 klass = self.resolve(config.pop('class'))
692 #Special case for handler which refers to another handler
693 if issubclass(klass, logging.handlers.MemoryHandler) and\
694 'target' in config:
695 try:
696 config['target'] = self.config['handlers'][config['target']]
697 except StandardError, e:
698 raise ValueError('Unable to set target handler '
699 '%r: %s' % (config['target'], e))
Vinay Sajip1adbee22010-03-06 15:56:03 +0000700 elif issubclass(klass, logging.handlers.SMTPHandler) and\
701 'mailhost' in config:
702 config['mailhost'] = self.as_tuple(config['mailhost'])
703 elif issubclass(klass, logging.handlers.SysLogHandler) and\
704 'address' in config:
705 config['address'] = self.as_tuple(config['address'])
Vinay Sajip28c382f2010-02-04 18:48:53 +0000706 factory = klass
707 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
708 try:
709 result = factory(**kwargs)
710 except TypeError, te:
711 if "'stream'" not in str(te):
712 raise
713 #The argument name changed from strm to stream
714 #Retry with old name.
715 #This is so that code can be used with older Python versions
716 #(e.g. by Django)
717 kwargs['strm'] = kwargs.pop('stream')
718 result = factory(**kwargs)
719 if formatter:
720 result.setFormatter(formatter)
721 if level is not None:
722 result.setLevel(logging._checkLevel(level))
723 if filters:
724 self.add_filters(result, filters)
725 return result
726
727 def add_handlers(self, logger, handlers):
728 """Add handlers to a logger from a list of names."""
729 for h in handlers:
730 try:
731 logger.addHandler(self.config['handlers'][h])
732 except StandardError, e:
733 raise ValueError('Unable to add handler %r: %s' % (h, e))
734
735 def common_logger_config(self, logger, config, incremental=False):
736 """
737 Perform configuration which is common to root and non-root loggers.
738 """
739 level = config.get('level', None)
740 if level is not None:
741 logger.setLevel(logging._checkLevel(level))
742 if not incremental:
743 #Remove any existing handlers
744 for h in logger.handlers[:]:
745 logger.removeHandler(h)
746 handlers = config.get('handlers', None)
747 if handlers:
748 self.add_handlers(logger, handlers)
749 filters = config.get('filters', None)
750 if filters:
751 self.add_filters(logger, filters)
752
753 def configure_logger(self, name, config, incremental=False):
754 """Configure a non-root logger from a dictionary."""
755 logger = logging.getLogger(name)
756 self.common_logger_config(logger, config, incremental)
757 propagate = config.get('propagate', None)
758 if propagate is not None:
759 logger.propagate = propagate
760
761 def configure_root(self, config, incremental=False):
762 """Configure a root logger from a dictionary."""
763 root = logging.getLogger()
764 self.common_logger_config(root, config, incremental)
765
766dictConfigClass = DictConfigurator
767
768def dictConfig(config):
769 """Configure logging using a dictionary."""
770 dictConfigClass(config).configure()
771
772
Guido van Rossum57102f82002-11-13 16:15:58 +0000773def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
774 """
775 Start up a socket server on the specified port, and listen for new
776 configurations.
777
778 These will be sent as a file suitable for processing by fileConfig().
779 Returns a Thread object on which you can call start() to start the server,
780 and which you can join() when appropriate. To stop the server, call
781 stopListening().
782 """
783 if not thread:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000784 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000785
786 class ConfigStreamHandler(StreamRequestHandler):
787 """
788 Handler for a logging configuration request.
789
790 It expects a completely new logging configuration and uses fileConfig
791 to install it.
792 """
793 def handle(self):
794 """
795 Handle a request.
796
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000797 Each request is expected to be a 4-byte length, packed using
798 struct.pack(">L", n), followed by the config file.
799 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000800 """
801 import tempfile
802 try:
803 conn = self.connection
804 chunk = conn.recv(4)
805 if len(chunk) == 4:
806 slen = struct.unpack(">L", chunk)[0]
807 chunk = self.connection.recv(slen)
808 while len(chunk) < slen:
809 chunk = chunk + conn.recv(slen - len(chunk))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000810 try:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000811 import json
812 d =json.loads(chunk)
813 assert isinstance(d, dict)
814 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000815 except:
Vinay Sajip28c382f2010-02-04 18:48:53 +0000816 #Apply new configuration.
817
818 file = cStringIO.StringIO(chunk)
819 try:
820 fileConfig(file)
821 except (KeyboardInterrupt, SystemExit):
822 raise
823 except:
824 traceback.print_exc()
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000825 if self.server.ready:
826 self.server.ready.set()
Guido van Rossum57102f82002-11-13 16:15:58 +0000827 except socket.error, e:
Vinay Sajip1c77b7f2009-10-10 20:32:36 +0000828 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000829 raise
830 else:
831 errcode = e.args[0]
832 if errcode != RESET_ERROR:
833 raise
834
835 class ConfigSocketReceiver(ThreadingTCPServer):
836 """
837 A simple TCP socket-based logging config receiver.
838 """
839
840 allow_reuse_address = 1
841
842 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000843 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000844 ThreadingTCPServer.__init__(self, (host, port), handler)
845 logging._acquireLock()
846 self.abort = 0
847 logging._releaseLock()
848 self.timeout = 1
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000849 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000850
851 def serve_until_stopped(self):
852 import select
853 abort = 0
854 while not abort:
855 rd, wr, ex = select.select([self.socket.fileno()],
856 [], [],
857 self.timeout)
858 if rd:
859 self.handle_request()
860 logging._acquireLock()
861 abort = self.abort
862 logging._releaseLock()
863
Benjamin Peterson239f1382010-02-06 22:08:15 +0000864 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000865
Benjamin Peterson239f1382010-02-06 22:08:15 +0000866 def __init__(self, rcvr, hdlr, port):
867 super(Server, self).__init__()
868 self.rcvr = rcvr
869 self.hdlr = hdlr
870 self.port = port
871 self.ready = threading.Event()
872
873 def run(self):
Vinay Sajipcfc43e92010-02-08 21:18:15 +0000874 server = self.rcvr(port=self.port, handler=self.hdlr,
875 ready=self.ready)
Vinay Sajip27a13702010-05-03 15:11:53 +0000876 if self.port == 0:
877 self.port = server.server_address[1]
Benjamin Peterson239f1382010-02-06 22:08:15 +0000878 self.ready.set()
879 global _listener
880 logging._acquireLock()
881 _listener = server
882 logging._releaseLock()
883 server.serve_until_stopped()
884
885 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000886
887def stopListening():
888 """
889 Stop the listening server which was created with a call to listen().
890 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000891 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000892 if _listener:
893 logging._acquireLock()
894 _listener.abort = 1
895 _listener = None
896 logging._releaseLock()