blob: beb51e5bb231ca4c27061b0409bb24e72fc7e86e [file] [log] [blame]
Vinay Sajipdb81c4c2010-02-25 23:13:06 +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 Sajipdb81c4c2010-02-25 23:13:06 +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 Sajipdb81c4c2010-02-25 23:13:06 +000027import sys, logging, logging.handlers, socket, struct, os, traceback, re
28import types, io
Vinay Sajip612df8e2005-02-18 11:54:46 +000029
30try:
Georg Brandl2067bfd2008-05-25 13:05:15 +000031 import _thread as thread
Vinay Sajip612df8e2005-02-18 11:54:46 +000032 import threading
33except ImportError:
34 thread = None
Guido van Rossum57102f82002-11-13 16:15:58 +000035
Alexandre Vassalottice261952008-05-12 02:31:37 +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
Georg Brandl472f2e22009-06-08 08:58:54 +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 """
Alexandre Vassalotti1d1eaa42008-05-14 22:59:42 +000062 import configparser
Guido van Rossum57102f82002-11-13 16:15:58 +000063
Alexandre Vassalotti1d1eaa42008-05-14 22:59:42 +000064 cp = configparser.ConfigParser(defaults)
Georg Brandl01e4d572010-02-06 22:27:51 +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()
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000076 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000077 # Handlers add themselves to logging._handlers
78 handlers = _install_handlers(cp, formatters)
Benjamin Petersonfea6a942008-07-02 16:11:42 +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."""
Neal Norwitz9d72bb42007-04-17 08:48:32 +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
Benjamin Petersonae5360b2008-09-08 23:05:23 +000098def _strip_spaces(alist):
99 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 {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000106 flist = flist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000107 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000108 formatters = {}
109 for form in flist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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 {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000135 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +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:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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 = ""
Georg Brandl3dbca812008-07-23 16:10:53 +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))
Neal Norwitzd9108552006-03-17 08:00:19 +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])
Benjamin Peterson41181742008-07-02 20:22:54 +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
Benjamin Petersonfea6a942008-07-02 16:11:42 +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")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000178 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000179 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):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000192 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000193 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000194 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000206 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +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:
Christian Heimes96f31632007-11-12 01:32:03 +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):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000244 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000245 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000246 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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.
Christian Heimes96f31632007-11-12 01:32:03 +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:
Christian Heimes96f31632007-11-12 01:32:03 +0000255 logger = root.manager.loggerDict[log]
256 if log in child_loggers:
257 logger.level = logging.NOTSET
258 logger.handlers = []
259 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000260 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000261 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000262
263
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000264IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
265
266
267def valid_ident(s):
268 m = IDENTIFIER.match(s)
269 if not m:
270 raise ValueError('Not a valid Python identifier: %r' % s)
271 return True
272
273
274# The ConvertingXXX classes are wrappers around standard Python containers,
275# and they serve to convert any suitable values in the container. The
276# conversion converts base dicts, lists and tuples to their wrapped
277# equivalents, whereas strings which match a conversion format are converted
278# appropriately.
279#
280# Each wrapper should have a configurator attribute holding the actual
281# configurator to use for conversion.
282
283class ConvertingDict(dict):
284 """A converting dictionary wrapper."""
285
286 def __getitem__(self, key):
287 value = dict.__getitem__(self, key)
288 result = self.configurator.convert(value)
289 #If the converted value is different, save for next time
290 if value is not result:
291 self[key] = result
292 if type(result) in (ConvertingDict, ConvertingList,
293 ConvertingTuple):
294 result.parent = self
295 result.key = key
296 return result
297
298 def get(self, key, default=None):
299 value = dict.get(self, key, default)
300 result = self.configurator.convert(value)
301 #If the converted value is different, save for next time
302 if value is not result:
303 self[key] = result
304 if type(result) in (ConvertingDict, ConvertingList,
305 ConvertingTuple):
306 result.parent = self
307 result.key = key
308 return result
309
310 def pop(self, key, default=None):
311 value = dict.pop(self, key, default)
312 result = self.configurator.convert(value)
313 if value is not result:
314 if type(result) in (ConvertingDict, ConvertingList,
315 ConvertingTuple):
316 result.parent = self
317 result.key = key
318 return result
319
320class ConvertingList(list):
321 """A converting list wrapper."""
322 def __getitem__(self, key):
323 value = list.__getitem__(self, key)
324 result = self.configurator.convert(value)
325 #If the converted value is different, save for next time
326 if value is not result:
327 self[key] = result
328 if type(result) in (ConvertingDict, ConvertingList,
329 ConvertingTuple):
330 result.parent = self
331 result.key = key
332 return result
333
334 def pop(self, idx=-1):
335 value = list.pop(self, idx)
336 result = self.configurator.convert(value)
337 if value is not result:
338 if type(result) in (ConvertingDict, ConvertingList,
339 ConvertingTuple):
340 result.parent = self
341 return result
342
343class ConvertingTuple(tuple):
344 """A converting tuple wrapper."""
345 def __getitem__(self, key):
346 value = tuple.__getitem__(self, key)
347 result = self.configurator.convert(value)
348 if value is not result:
349 if type(result) in (ConvertingDict, ConvertingList,
350 ConvertingTuple):
351 result.parent = self
352 result.key = key
353 return result
354
355class BaseConfigurator(object):
356 """
357 The configurator base class which defines some useful defaults.
358 """
359
360 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
361
362 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
363 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
364 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
365 DIGIT_PATTERN = re.compile(r'^\d+$')
366
367 value_converters = {
368 'ext' : 'ext_convert',
369 'cfg' : 'cfg_convert',
370 }
371
372 # We might want to use a different one, e.g. importlib
373 importer = __import__
374
375 def __init__(self, config):
376 self.config = ConvertingDict(config)
377 self.config.configurator = self
378
379 def resolve(self, s):
380 """
381 Resolve strings to objects using standard import and attribute
382 syntax.
383 """
384 name = s.split('.')
385 used = name.pop(0)
386 found = self.importer(used)
387 for frag in name:
388 used += '.' + frag
389 try:
390 found = getattr(found, frag)
391 except AttributeError:
392 self.importer(used)
393 found = getattr(found, frag)
394 return found
395
396 def ext_convert(self, value):
397 """Default converter for the ext:// protocol."""
398 return self.resolve(value)
399
400 def cfg_convert(self, value):
401 """Default converter for the cfg:// protocol."""
402 rest = value
403 m = self.WORD_PATTERN.match(rest)
404 if m is None:
405 raise ValueError("Unable to convert %r" % value)
406 else:
407 rest = rest[m.end():]
408 d = self.config[m.groups()[0]]
409 #print d, rest
410 while rest:
411 m = self.DOT_PATTERN.match(rest)
412 if m:
413 d = d[m.groups()[0]]
414 else:
415 m = self.INDEX_PATTERN.match(rest)
416 if m:
417 idx = m.groups()[0]
418 if not self.DIGIT_PATTERN.match(idx):
419 d = d[idx]
420 else:
421 try:
422 n = int(idx) # try as number first (most likely)
423 d = d[n]
424 except TypeError:
425 d = d[idx]
426 if m:
427 rest = rest[m.end():]
428 else:
429 raise ValueError('Unable to convert '
430 '%r at %r' % (value, rest))
431 #rest should be empty
432 return d
433
434 def convert(self, value):
435 """
436 Convert values to an appropriate type. dicts, lists and tuples are
437 replaced by their converting alternatives. Strings are checked to
438 see if they have a conversion format and are converted if they do.
439 """
440 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
441 value = ConvertingDict(value)
442 value.configurator = self
443 elif not isinstance(value, ConvertingList) and isinstance(value, list):
444 value = ConvertingList(value)
445 value.configurator = self
446 elif not isinstance(value, ConvertingTuple) and\
447 isinstance(value, tuple):
448 value = ConvertingTuple(value)
449 value.configurator = self
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000450 elif isinstance(value, str): # str for py3k
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000451 m = self.CONVERT_PATTERN.match(value)
452 if m:
453 d = m.groupdict()
454 prefix = d['prefix']
455 converter = self.value_converters.get(prefix, None)
456 if converter:
457 suffix = d['suffix']
458 converter = getattr(self, converter)
459 value = converter(suffix)
460 return value
461
462 def configure_custom(self, config):
463 """Configure an object with a user-supplied factory."""
464 c = config.pop('()')
465 if not hasattr(c, '__call__'):
466 c = self.resolve(c)
467 props = config.pop('.', None)
468 # Check for valid identifiers
469 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
470 result = c(**kwargs)
471 if props:
472 for name, value in props.items():
473 setattr(result, name, value)
474 return result
475
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000476 def as_tuple(self, value):
477 """Utility function which converts lists to tuples."""
478 if isinstance(value, list):
479 value = tuple(value)
480 return value
481
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000482class DictConfigurator(BaseConfigurator):
483 """
484 Configure logging using a dictionary-like object to describe the
485 configuration.
486 """
487
488 def configure(self):
489 """Do the configuration."""
490
491 config = self.config
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000492 if 'version' not in config:
493 raise ValueError("dictionary doesn't specify a version")
494 if config['version'] != 1:
495 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000496 incremental = config.pop('incremental', False)
497 EMPTY_DICT = {}
498 logging._acquireLock()
499 try:
500 if incremental:
501 handlers = config.get('handlers', EMPTY_DICT)
502 for name in handlers:
503 if name not in logging._handlers:
504 raise ValueError('No handler found with '
505 'name %r' % name)
506 else:
507 try:
508 handler = logging._handlers[name]
509 handler_config = handlers[name]
510 level = handler_config.get('level', None)
511 if level:
512 handler.setLevel(logging._checkLevel(level))
513 except Exception as e:
514 raise ValueError('Unable to configure handler '
515 '%r: %s' % (name, e))
516 loggers = config.get('loggers', EMPTY_DICT)
517 for name in loggers:
518 try:
519 self.configure_logger(name, loggers[name], True)
520 except Exception as e:
521 raise ValueError('Unable to configure logger '
522 '%r: %s' % (name, e))
523 root = config.get('root', None)
524 if root:
525 try:
526 self.configure_root(root, True)
527 except Exception as e:
528 raise ValueError('Unable to configure root '
529 'logger: %s' % e)
530 else:
531 disable_existing = config.pop('disable_existing_loggers', True)
532
533 logging._handlers.clear()
534 del logging._handlerList[:]
535
536 # Do formatters first - they don't refer to anything else
537 formatters = config.get('formatters', EMPTY_DICT)
538 for name in formatters:
539 try:
540 formatters[name] = self.configure_formatter(
541 formatters[name])
542 except Exception as e:
543 raise ValueError('Unable to configure '
544 'formatter %r: %s' % (name, e))
545 # Next, do filters - they don't refer to anything else, either
546 filters = config.get('filters', EMPTY_DICT)
547 for name in filters:
548 try:
549 filters[name] = self.configure_filter(filters[name])
550 except Exception as e:
551 raise ValueError('Unable to configure '
552 'filter %r: %s' % (name, e))
553
554 # Next, do handlers - they refer to formatters and filters
555 # As handlers can refer to other handlers, sort the keys
556 # to allow a deterministic order of configuration
557 handlers = config.get('handlers', EMPTY_DICT)
558 for name in sorted(handlers):
559 try:
560 handler = self.configure_handler(handlers[name])
561 handler.name = name
562 handlers[name] = handler
563 except Exception as e:
564 raise ValueError('Unable to configure handler '
565 '%r: %s' % (name, e))
566 # Next, do loggers - they refer to handlers and filters
567
568 #we don't want to lose the existing loggers,
569 #since other threads may have pointers to them.
570 #existing is set to contain all existing loggers,
571 #and as we go through the new configuration we
572 #remove any which are configured. At the end,
573 #what's left in existing is the set of loggers
574 #which were in the previous configuration but
575 #which are not in the new configuration.
576 root = logging.root
577 existing = list(root.manager.loggerDict.keys())
578 #The list needs to be sorted so that we can
579 #avoid disabling child loggers of explicitly
580 #named loggers. With a sorted list it is easier
581 #to find the child loggers.
582 existing.sort()
583 #We'll keep the list of existing loggers
584 #which are children of named loggers here...
585 child_loggers = []
586 #now set up the new ones...
587 loggers = config.get('loggers', EMPTY_DICT)
588 for name in loggers:
589 if name in existing:
590 i = existing.index(name)
591 prefixed = name + "."
592 pflen = len(prefixed)
593 num_existing = len(existing)
594 i = i + 1 # look at the entry after name
595 while (i < num_existing) and\
596 (existing[i][:pflen] == prefixed):
597 child_loggers.append(existing[i])
598 i = i + 1
599 existing.remove(name)
600 try:
601 self.configure_logger(name, loggers[name])
602 except Exception as e:
603 raise ValueError('Unable to configure logger '
604 '%r: %s' % (name, e))
605
606 #Disable any old loggers. There's no point deleting
607 #them as other threads may continue to hold references
608 #and by disabling them, you stop them doing any logging.
609 #However, don't disable children of named loggers, as that's
610 #probably not what was intended by the user.
611 for log in existing:
612 logger = root.manager.loggerDict[log]
613 if log in child_loggers:
614 logger.level = logging.NOTSET
615 logger.handlers = []
616 logger.propagate = True
617 elif disable_existing:
618 logger.disabled = True
619
620 # And finally, do the root logger
621 root = config.get('root', None)
622 if root:
623 try:
624 self.configure_root(root)
625 except Exception as e:
626 raise ValueError('Unable to configure root '
627 'logger: %s' % e)
628 finally:
629 logging._releaseLock()
630
631 def configure_formatter(self, config):
632 """Configure a formatter from a dictionary."""
633 if '()' in config:
634 factory = config['()'] # for use in exception handler
635 try:
636 result = self.configure_custom(config)
637 except TypeError as te:
638 if "'format'" not in str(te):
639 raise
640 #Name of parameter changed from fmt to format.
641 #Retry with old name.
642 #This is so that code can be used with older Python versions
643 #(e.g. by Django)
644 config['fmt'] = config.pop('format')
645 config['()'] = factory
646 result = self.configure_custom(config)
647 else:
648 fmt = config.get('format', None)
649 dfmt = config.get('datefmt', None)
650 result = logging.Formatter(fmt, dfmt)
651 return result
652
653 def configure_filter(self, config):
654 """Configure a filter from a dictionary."""
655 if '()' in config:
656 result = self.configure_custom(config)
657 else:
658 name = config.get('name', '')
659 result = logging.Filter(name)
660 return result
661
662 def add_filters(self, filterer, filters):
663 """Add filters to a filterer from a list of names."""
664 for f in filters:
665 try:
666 filterer.addFilter(self.config['filters'][f])
667 except Exception as e:
668 raise ValueError('Unable to add filter %r: %s' % (f, e))
669
670 def configure_handler(self, config):
671 """Configure a handler from a dictionary."""
672 formatter = config.pop('formatter', None)
673 if formatter:
674 try:
675 formatter = self.config['formatters'][formatter]
676 except Exception as e:
677 raise ValueError('Unable to set formatter '
678 '%r: %s' % (formatter, e))
679 level = config.pop('level', None)
680 filters = config.pop('filters', None)
681 if '()' in config:
682 c = config.pop('()')
683 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
684 c = self.resolve(c)
685 factory = c
686 else:
687 klass = self.resolve(config.pop('class'))
688 #Special case for handler which refers to another handler
689 if issubclass(klass, logging.handlers.MemoryHandler) and\
690 'target' in config:
691 try:
692 config['target'] = self.config['handlers'][config['target']]
693 except Exception as e:
694 raise ValueError('Unable to set target handler '
695 '%r: %s' % (config['target'], e))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000696 elif issubclass(klass, logging.handlers.SMTPHandler) and\
697 'mailhost' in config:
698 config['mailhost'] = self.as_tuple(config['mailhost'])
699 elif issubclass(klass, logging.handlers.SysLogHandler) and\
700 'address' in config:
701 config['address'] = self.as_tuple(config['address'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000702 factory = klass
703 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
704 try:
705 result = factory(**kwargs)
706 except TypeError as te:
707 if "'stream'" not in str(te):
708 raise
709 #The argument name changed from strm to stream
710 #Retry with old name.
711 #This is so that code can be used with older Python versions
712 #(e.g. by Django)
713 kwargs['strm'] = kwargs.pop('stream')
714 result = factory(**kwargs)
715 if formatter:
716 result.setFormatter(formatter)
717 if level is not None:
718 result.setLevel(logging._checkLevel(level))
719 if filters:
720 self.add_filters(result, filters)
721 return result
722
723 def add_handlers(self, logger, handlers):
724 """Add handlers to a logger from a list of names."""
725 for h in handlers:
726 try:
727 logger.addHandler(self.config['handlers'][h])
728 except Exception as e:
729 raise ValueError('Unable to add handler %r: %s' % (h, e))
730
731 def common_logger_config(self, logger, config, incremental=False):
732 """
733 Perform configuration which is common to root and non-root loggers.
734 """
735 level = config.get('level', None)
736 if level is not None:
737 logger.setLevel(logging._checkLevel(level))
738 if not incremental:
739 #Remove any existing handlers
740 for h in logger.handlers[:]:
741 logger.removeHandler(h)
742 handlers = config.get('handlers', None)
743 if handlers:
744 self.add_handlers(logger, handlers)
745 filters = config.get('filters', None)
746 if filters:
747 self.add_filters(logger, filters)
748
749 def configure_logger(self, name, config, incremental=False):
750 """Configure a non-root logger from a dictionary."""
751 logger = logging.getLogger(name)
752 self.common_logger_config(logger, config, incremental)
753 propagate = config.get('propagate', None)
754 if propagate is not None:
755 logger.propagate = propagate
756
757 def configure_root(self, config, incremental=False):
758 """Configure a root logger from a dictionary."""
759 root = logging.getLogger()
760 self.common_logger_config(root, config, incremental)
761
762dictConfigClass = DictConfigurator
763
764def dictConfig(config):
765 """Configure logging using a dictionary."""
766 dictConfigClass(config).configure()
767
768
Guido van Rossum57102f82002-11-13 16:15:58 +0000769def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
770 """
771 Start up a socket server on the specified port, and listen for new
772 configurations.
773
774 These will be sent as a file suitable for processing by fileConfig().
775 Returns a Thread object on which you can call start() to start the server,
776 and which you can join() when appropriate. To stop the server, call
777 stopListening().
778 """
779 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000780 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000781
782 class ConfigStreamHandler(StreamRequestHandler):
783 """
784 Handler for a logging configuration request.
785
786 It expects a completely new logging configuration and uses fileConfig
787 to install it.
788 """
789 def handle(self):
790 """
791 Handle a request.
792
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000793 Each request is expected to be a 4-byte length, packed using
794 struct.pack(">L", n), followed by the config file.
795 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000796 """
797 import tempfile
798 try:
799 conn = self.connection
800 chunk = conn.recv(4)
801 if len(chunk) == 4:
802 slen = struct.unpack(">L", chunk)[0]
803 chunk = self.connection.recv(slen)
804 while len(chunk) < slen:
805 chunk = chunk + conn.recv(slen - len(chunk))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000806 chunk = chunk.decode("utf-8")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000807 try:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +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 Sajipdb81c4c2010-02-25 23:13:06 +0000813 #Apply new configuration.
814
815 file = io.StringIO(chunk)
816 try:
817 fileConfig(file)
818 except (KeyboardInterrupt, SystemExit):
819 raise
820 except:
821 traceback.print_exc()
822 if self.server.ready:
823 self.server.ready.set()
Guido van Rossumb940e112007-01-10 16:19:56 +0000824 except socket.error as e:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +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 Sajipdb81c4c2010-02-25 23:13:06 +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 Sajipdb81c4c2010-02-25 23:13:06 +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
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000861 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000862
Vinay Sajipdb81c4c2010-02-25 23:13:06 +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):
871 server = self.rcvr(port=self.port, handler=self.hdlr,
872 ready=self.ready)
873 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()