blob: 63193b1c7f8d8ccd0c5cfe60a7016b843ab34e7d [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
Benjamin Peterson22005fc2010-04-11 16:25:06 +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 {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000109 flist = flist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000110 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000111 formatters = {}
112 for form in flist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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 {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000138 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +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:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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 = ""
Georg Brandl3dbca812008-07-23 16:10:53 +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))
Neal Norwitzd9108552006-03-17 08:00:19 +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])
Benjamin Peterson41181742008-07-02 20:22:54 +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
Benjamin Petersonfea6a942008-07-02 16:11:42 +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")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000181 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000182 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):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000195 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000196 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000197 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000209 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +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.
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000214 existing.sort(key=_encoded)
Christian Heimes96f31632007-11-12 01:32:03 +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:
Christian Heimes96f31632007-11-12 01:32:03 +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):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000247 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000248 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000249 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +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.
Christian Heimes96f31632007-11-12 01:32:03 +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:
Christian Heimes96f31632007-11-12 01:32:03 +0000258 logger = root.manager.loggerDict[log]
259 if log in child_loggers:
260 logger.level = logging.NOTSET
261 logger.handlers = []
262 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000263 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000264 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000265
266
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000267IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
268
269
270def valid_ident(s):
271 m = IDENTIFIER.match(s)
272 if not m:
273 raise ValueError('Not a valid Python identifier: %r' % s)
274 return True
275
276
277# The ConvertingXXX classes are wrappers around standard Python containers,
278# and they serve to convert any suitable values in the container. The
279# conversion converts base dicts, lists and tuples to their wrapped
280# equivalents, whereas strings which match a conversion format are converted
281# appropriately.
282#
283# Each wrapper should have a configurator attribute holding the actual
284# configurator to use for conversion.
285
286class ConvertingDict(dict):
287 """A converting dictionary wrapper."""
288
289 def __getitem__(self, key):
290 value = dict.__getitem__(self, key)
291 result = self.configurator.convert(value)
292 #If the converted value is different, save for next time
293 if value is not result:
294 self[key] = result
295 if type(result) in (ConvertingDict, ConvertingList,
296 ConvertingTuple):
297 result.parent = self
298 result.key = key
299 return result
300
301 def get(self, key, default=None):
302 value = dict.get(self, key, default)
303 result = self.configurator.convert(value)
304 #If the converted value is different, save for next time
305 if value is not result:
306 self[key] = result
307 if type(result) in (ConvertingDict, ConvertingList,
308 ConvertingTuple):
309 result.parent = self
310 result.key = key
311 return result
312
313 def pop(self, key, default=None):
314 value = dict.pop(self, key, default)
315 result = self.configurator.convert(value)
316 if value is not result:
317 if type(result) in (ConvertingDict, ConvertingList,
318 ConvertingTuple):
319 result.parent = self
320 result.key = key
321 return result
322
323class ConvertingList(list):
324 """A converting list wrapper."""
325 def __getitem__(self, key):
326 value = list.__getitem__(self, key)
327 result = self.configurator.convert(value)
328 #If the converted value is different, save for next time
329 if value is not result:
330 self[key] = result
331 if type(result) in (ConvertingDict, ConvertingList,
332 ConvertingTuple):
333 result.parent = self
334 result.key = key
335 return result
336
337 def pop(self, idx=-1):
338 value = list.pop(self, idx)
339 result = self.configurator.convert(value)
340 if value is not result:
341 if type(result) in (ConvertingDict, ConvertingList,
342 ConvertingTuple):
343 result.parent = self
344 return result
345
346class ConvertingTuple(tuple):
347 """A converting tuple wrapper."""
348 def __getitem__(self, key):
349 value = tuple.__getitem__(self, key)
350 result = self.configurator.convert(value)
351 if value is not result:
352 if type(result) in (ConvertingDict, ConvertingList,
353 ConvertingTuple):
354 result.parent = self
355 result.key = key
356 return result
357
358class BaseConfigurator(object):
359 """
360 The configurator base class which defines some useful defaults.
361 """
362
363 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
364
365 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
366 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
367 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
368 DIGIT_PATTERN = re.compile(r'^\d+$')
369
370 value_converters = {
371 'ext' : 'ext_convert',
372 'cfg' : 'cfg_convert',
373 }
374
375 # We might want to use a different one, e.g. importlib
Brett Cannonc2368502010-06-12 00:39:28 +0000376 importer = staticmethod(__import__)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000377
378 def __init__(self, config):
379 self.config = ConvertingDict(config)
380 self.config.configurator = self
381
382 def resolve(self, s):
383 """
384 Resolve strings to objects using standard import and attribute
385 syntax.
386 """
387 name = s.split('.')
388 used = name.pop(0)
Benjamin Petersona82addb2010-06-27 20:54:28 +0000389 try:
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 except ImportError:
400 e, tb = sys.exc_info()[1:]
401 v = ValueError('Cannot resolve %r: %s' % (s, e))
402 v.__cause__, v.__traceback__ = e, tb
403 raise v
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000404
405 def ext_convert(self, value):
406 """Default converter for the ext:// protocol."""
407 return self.resolve(value)
408
409 def cfg_convert(self, value):
410 """Default converter for the cfg:// protocol."""
411 rest = value
412 m = self.WORD_PATTERN.match(rest)
413 if m is None:
414 raise ValueError("Unable to convert %r" % value)
415 else:
416 rest = rest[m.end():]
417 d = self.config[m.groups()[0]]
418 #print d, rest
419 while rest:
420 m = self.DOT_PATTERN.match(rest)
421 if m:
422 d = d[m.groups()[0]]
423 else:
424 m = self.INDEX_PATTERN.match(rest)
425 if m:
426 idx = m.groups()[0]
427 if not self.DIGIT_PATTERN.match(idx):
428 d = d[idx]
429 else:
430 try:
431 n = int(idx) # try as number first (most likely)
432 d = d[n]
433 except TypeError:
434 d = d[idx]
435 if m:
436 rest = rest[m.end():]
437 else:
438 raise ValueError('Unable to convert '
439 '%r at %r' % (value, rest))
440 #rest should be empty
441 return d
442
443 def convert(self, value):
444 """
445 Convert values to an appropriate type. dicts, lists and tuples are
446 replaced by their converting alternatives. Strings are checked to
447 see if they have a conversion format and are converted if they do.
448 """
449 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
450 value = ConvertingDict(value)
451 value.configurator = self
452 elif not isinstance(value, ConvertingList) and isinstance(value, list):
453 value = ConvertingList(value)
454 value.configurator = self
455 elif not isinstance(value, ConvertingTuple) and\
456 isinstance(value, tuple):
457 value = ConvertingTuple(value)
458 value.configurator = self
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000459 elif isinstance(value, str): # str for py3k
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000460 m = self.CONVERT_PATTERN.match(value)
461 if m:
462 d = m.groupdict()
463 prefix = d['prefix']
464 converter = self.value_converters.get(prefix, None)
465 if converter:
466 suffix = d['suffix']
467 converter = getattr(self, converter)
468 value = converter(suffix)
469 return value
470
471 def configure_custom(self, config):
472 """Configure an object with a user-supplied factory."""
473 c = config.pop('()')
474 if not hasattr(c, '__call__'):
475 c = self.resolve(c)
476 props = config.pop('.', None)
477 # Check for valid identifiers
478 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
479 result = c(**kwargs)
480 if props:
481 for name, value in props.items():
482 setattr(result, name, value)
483 return result
484
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000485 def as_tuple(self, value):
486 """Utility function which converts lists to tuples."""
487 if isinstance(value, list):
488 value = tuple(value)
489 return value
490
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000491class DictConfigurator(BaseConfigurator):
492 """
493 Configure logging using a dictionary-like object to describe the
494 configuration.
495 """
496
497 def configure(self):
498 """Do the configuration."""
499
500 config = self.config
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000501 if 'version' not in config:
502 raise ValueError("dictionary doesn't specify a version")
503 if config['version'] != 1:
504 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000505 incremental = config.pop('incremental', False)
506 EMPTY_DICT = {}
507 logging._acquireLock()
508 try:
509 if incremental:
510 handlers = config.get('handlers', EMPTY_DICT)
511 for name in handlers:
512 if name not in logging._handlers:
513 raise ValueError('No handler found with '
514 'name %r' % name)
515 else:
516 try:
517 handler = logging._handlers[name]
518 handler_config = handlers[name]
519 level = handler_config.get('level', None)
520 if level:
521 handler.setLevel(logging._checkLevel(level))
522 except Exception as e:
523 raise ValueError('Unable to configure handler '
524 '%r: %s' % (name, e))
525 loggers = config.get('loggers', EMPTY_DICT)
526 for name in loggers:
527 try:
528 self.configure_logger(name, loggers[name], True)
529 except Exception as e:
530 raise ValueError('Unable to configure logger '
531 '%r: %s' % (name, e))
532 root = config.get('root', None)
533 if root:
534 try:
535 self.configure_root(root, True)
536 except Exception as e:
537 raise ValueError('Unable to configure root '
538 'logger: %s' % e)
539 else:
540 disable_existing = config.pop('disable_existing_loggers', True)
541
542 logging._handlers.clear()
543 del logging._handlerList[:]
544
545 # Do formatters first - they don't refer to anything else
546 formatters = config.get('formatters', EMPTY_DICT)
547 for name in formatters:
548 try:
549 formatters[name] = self.configure_formatter(
550 formatters[name])
551 except Exception as e:
552 raise ValueError('Unable to configure '
553 'formatter %r: %s' % (name, e))
554 # Next, do filters - they don't refer to anything else, either
555 filters = config.get('filters', EMPTY_DICT)
556 for name in filters:
557 try:
558 filters[name] = self.configure_filter(filters[name])
559 except Exception as e:
560 raise ValueError('Unable to configure '
561 'filter %r: %s' % (name, e))
562
563 # Next, do handlers - they refer to formatters and filters
564 # As handlers can refer to other handlers, sort the keys
565 # to allow a deterministic order of configuration
566 handlers = config.get('handlers', EMPTY_DICT)
567 for name in sorted(handlers):
568 try:
569 handler = self.configure_handler(handlers[name])
570 handler.name = name
571 handlers[name] = handler
572 except Exception as e:
573 raise ValueError('Unable to configure handler '
574 '%r: %s' % (name, e))
575 # Next, do loggers - they refer to handlers and filters
576
577 #we don't want to lose the existing loggers,
578 #since other threads may have pointers to them.
579 #existing is set to contain all existing loggers,
580 #and as we go through the new configuration we
581 #remove any which are configured. At the end,
582 #what's left in existing is the set of loggers
583 #which were in the previous configuration but
584 #which are not in the new configuration.
585 root = logging.root
586 existing = list(root.manager.loggerDict.keys())
587 #The list needs to be sorted so that we can
588 #avoid disabling child loggers of explicitly
589 #named loggers. With a sorted list it is easier
590 #to find the child loggers.
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000591 existing.sort(key=_encoded)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000592 #We'll keep the list of existing loggers
593 #which are children of named loggers here...
594 child_loggers = []
595 #now set up the new ones...
596 loggers = config.get('loggers', EMPTY_DICT)
597 for name in loggers:
598 if name in existing:
599 i = existing.index(name)
600 prefixed = name + "."
601 pflen = len(prefixed)
602 num_existing = len(existing)
603 i = i + 1 # look at the entry after name
604 while (i < num_existing) and\
605 (existing[i][:pflen] == prefixed):
606 child_loggers.append(existing[i])
607 i = i + 1
608 existing.remove(name)
609 try:
610 self.configure_logger(name, loggers[name])
611 except Exception as e:
612 raise ValueError('Unable to configure logger '
613 '%r: %s' % (name, e))
614
615 #Disable any old loggers. There's no point deleting
616 #them as other threads may continue to hold references
617 #and by disabling them, you stop them doing any logging.
618 #However, don't disable children of named loggers, as that's
619 #probably not what was intended by the user.
620 for log in existing:
621 logger = root.manager.loggerDict[log]
622 if log in child_loggers:
623 logger.level = logging.NOTSET
624 logger.handlers = []
625 logger.propagate = True
626 elif disable_existing:
627 logger.disabled = True
628
629 # And finally, do the root logger
630 root = config.get('root', None)
631 if root:
632 try:
633 self.configure_root(root)
634 except Exception as e:
635 raise ValueError('Unable to configure root '
636 'logger: %s' % e)
637 finally:
638 logging._releaseLock()
639
640 def configure_formatter(self, config):
641 """Configure a formatter from a dictionary."""
642 if '()' in config:
643 factory = config['()'] # for use in exception handler
644 try:
645 result = self.configure_custom(config)
646 except TypeError as te:
647 if "'format'" not in str(te):
648 raise
649 #Name of parameter changed from fmt to format.
650 #Retry with old name.
651 #This is so that code can be used with older Python versions
652 #(e.g. by Django)
653 config['fmt'] = config.pop('format')
654 config['()'] = factory
655 result = self.configure_custom(config)
656 else:
657 fmt = config.get('format', None)
658 dfmt = config.get('datefmt', None)
659 result = logging.Formatter(fmt, dfmt)
660 return result
661
662 def configure_filter(self, config):
663 """Configure a filter from a dictionary."""
664 if '()' in config:
665 result = self.configure_custom(config)
666 else:
667 name = config.get('name', '')
668 result = logging.Filter(name)
669 return result
670
671 def add_filters(self, filterer, filters):
672 """Add filters to a filterer from a list of names."""
673 for f in filters:
674 try:
675 filterer.addFilter(self.config['filters'][f])
676 except Exception as e:
677 raise ValueError('Unable to add filter %r: %s' % (f, e))
678
679 def configure_handler(self, config):
680 """Configure a handler from a dictionary."""
681 formatter = config.pop('formatter', None)
682 if formatter:
683 try:
684 formatter = self.config['formatters'][formatter]
685 except Exception as e:
686 raise ValueError('Unable to set formatter '
687 '%r: %s' % (formatter, e))
688 level = config.pop('level', None)
689 filters = config.pop('filters', None)
690 if '()' in config:
691 c = config.pop('()')
692 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
693 c = self.resolve(c)
694 factory = c
695 else:
696 klass = self.resolve(config.pop('class'))
697 #Special case for handler which refers to another handler
698 if issubclass(klass, logging.handlers.MemoryHandler) and\
699 'target' in config:
700 try:
701 config['target'] = self.config['handlers'][config['target']]
702 except Exception as e:
703 raise ValueError('Unable to set target handler '
704 '%r: %s' % (config['target'], e))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000705 elif issubclass(klass, logging.handlers.SMTPHandler) and\
706 'mailhost' in config:
707 config['mailhost'] = self.as_tuple(config['mailhost'])
708 elif issubclass(klass, logging.handlers.SysLogHandler) and\
709 'address' in config:
710 config['address'] = self.as_tuple(config['address'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000711 factory = klass
712 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
713 try:
714 result = factory(**kwargs)
715 except TypeError as te:
716 if "'stream'" not in str(te):
717 raise
718 #The argument name changed from strm to stream
719 #Retry with old name.
720 #This is so that code can be used with older Python versions
721 #(e.g. by Django)
722 kwargs['strm'] = kwargs.pop('stream')
723 result = factory(**kwargs)
724 if formatter:
725 result.setFormatter(formatter)
726 if level is not None:
727 result.setLevel(logging._checkLevel(level))
728 if filters:
729 self.add_filters(result, filters)
730 return result
731
732 def add_handlers(self, logger, handlers):
733 """Add handlers to a logger from a list of names."""
734 for h in handlers:
735 try:
736 logger.addHandler(self.config['handlers'][h])
737 except Exception as e:
738 raise ValueError('Unable to add handler %r: %s' % (h, e))
739
740 def common_logger_config(self, logger, config, incremental=False):
741 """
742 Perform configuration which is common to root and non-root loggers.
743 """
744 level = config.get('level', None)
745 if level is not None:
746 logger.setLevel(logging._checkLevel(level))
747 if not incremental:
748 #Remove any existing handlers
749 for h in logger.handlers[:]:
750 logger.removeHandler(h)
751 handlers = config.get('handlers', None)
752 if handlers:
753 self.add_handlers(logger, handlers)
754 filters = config.get('filters', None)
755 if filters:
756 self.add_filters(logger, filters)
757
758 def configure_logger(self, name, config, incremental=False):
759 """Configure a non-root logger from a dictionary."""
760 logger = logging.getLogger(name)
761 self.common_logger_config(logger, config, incremental)
762 propagate = config.get('propagate', None)
763 if propagate is not None:
764 logger.propagate = propagate
765
766 def configure_root(self, config, incremental=False):
767 """Configure a root logger from a dictionary."""
768 root = logging.getLogger()
769 self.common_logger_config(root, config, incremental)
770
771dictConfigClass = DictConfigurator
772
773def dictConfig(config):
774 """Configure logging using a dictionary."""
775 dictConfigClass(config).configure()
776
777
Guido van Rossum57102f82002-11-13 16:15:58 +0000778def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
779 """
780 Start up a socket server on the specified port, and listen for new
781 configurations.
782
783 These will be sent as a file suitable for processing by fileConfig().
784 Returns a Thread object on which you can call start() to start the server,
785 and which you can join() when appropriate. To stop the server, call
786 stopListening().
787 """
788 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000789 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000790
791 class ConfigStreamHandler(StreamRequestHandler):
792 """
793 Handler for a logging configuration request.
794
795 It expects a completely new logging configuration and uses fileConfig
796 to install it.
797 """
798 def handle(self):
799 """
800 Handle a request.
801
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000802 Each request is expected to be a 4-byte length, packed using
803 struct.pack(">L", n), followed by the config file.
804 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000805 """
806 import tempfile
807 try:
808 conn = self.connection
809 chunk = conn.recv(4)
810 if len(chunk) == 4:
811 slen = struct.unpack(">L", chunk)[0]
812 chunk = self.connection.recv(slen)
813 while len(chunk) < slen:
814 chunk = chunk + conn.recv(slen - len(chunk))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000815 chunk = chunk.decode("utf-8")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000816 try:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000817 import json
818 d =json.loads(chunk)
819 assert isinstance(d, dict)
820 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000821 except:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000822 #Apply new configuration.
823
824 file = io.StringIO(chunk)
825 try:
826 fileConfig(file)
827 except (KeyboardInterrupt, SystemExit):
828 raise
829 except:
830 traceback.print_exc()
831 if self.server.ready:
832 self.server.ready.set()
Guido van Rossumb940e112007-01-10 16:19:56 +0000833 except socket.error as e:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000834 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000835 raise
836 else:
837 errcode = e.args[0]
838 if errcode != RESET_ERROR:
839 raise
840
841 class ConfigSocketReceiver(ThreadingTCPServer):
842 """
843 A simple TCP socket-based logging config receiver.
844 """
845
846 allow_reuse_address = 1
847
848 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000849 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000850 ThreadingTCPServer.__init__(self, (host, port), handler)
851 logging._acquireLock()
852 self.abort = 0
853 logging._releaseLock()
854 self.timeout = 1
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000855 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000856
857 def serve_until_stopped(self):
858 import select
859 abort = 0
860 while not abort:
861 rd, wr, ex = select.select([self.socket.fileno()],
862 [], [],
863 self.timeout)
864 if rd:
865 self.handle_request()
866 logging._acquireLock()
867 abort = self.abort
868 logging._releaseLock()
869
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000870 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000871
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000872 def __init__(self, rcvr, hdlr, port):
873 super(Server, self).__init__()
874 self.rcvr = rcvr
875 self.hdlr = hdlr
876 self.port = port
877 self.ready = threading.Event()
878
879 def run(self):
880 server = self.rcvr(port=self.port, handler=self.hdlr,
881 ready=self.ready)
Benjamin Petersona82addb2010-06-27 20:54:28 +0000882 if self.port == 0:
883 self.port = server.server_address[1]
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000884 self.ready.set()
885 global _listener
886 logging._acquireLock()
887 _listener = server
888 logging._releaseLock()
889 server.serve_until_stopped()
890
891 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000892
893def stopListening():
894 """
895 Stop the listening server which was created with a call to listen().
896 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000897 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000898 if _listener:
899 logging._acquireLock()
900 _listener.abort = 1
901 _listener = None
902 logging._releaseLock()