blob: cc75e94140fe763c48d148be81fc7882f7a59dff [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'):
Florent Xiclunadc692742010-08-15 20:16:27 +000066 cp.read_file(fname)
Guido van Rossum57102f82002-11-13 16:15:58 +000067 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
Vinay Sajipec1cd1c2010-08-30 19:02:14 +0000175def _handle_existing_loggers(existing, child_loggers, disable_existing):
176 """
177 When (re)configuring logging, handle loggers which were in the previous
178 configuration but are not in the new configuration. There's no point
179 deleting them as other threads may continue to hold references to them;
180 and by disabling them, you stop them doing any logging.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000181
Vinay Sajipec1cd1c2010-08-30 19:02:14 +0000182 However, don't disable children of named loggers, as that's probably not
183 what was intended by the user. Also, allow existing loggers to NOT be
184 disabled if disable_existing is false.
185 """
186 root = logging.root
187 for log in existing:
188 logger = root.manager.loggerDict[log]
189 if log in child_loggers:
190 logger.level = logging.NOTSET
191 logger.handlers = []
192 logger.propagate = True
193 elif disable_existing:
194 logger.disabled = True
195
196def _install_loggers(cp, handlers, disable_existing):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000197 """Create and install loggers"""
198
199 # configure the root first
200 llist = cp.get("loggers", "keys")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000201 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000202 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000203 llist.remove("root")
204 sectname = "logger_root"
205 root = logging.root
206 log = root
207 opts = cp.options(sectname)
208 if "level" in opts:
209 level = cp.get(sectname, "level")
210 log.setLevel(logging._levelNames[level])
211 for h in root.handlers[:]:
212 root.removeHandler(h)
213 hlist = cp.get(sectname, "handlers")
214 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000215 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000216 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000217 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000218 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000219
220 #and now the others...
221 #we don't want to lose the existing loggers,
222 #since other threads may have pointers to them.
223 #existing is set to contain all existing loggers,
224 #and as we go through the new configuration we
225 #remove any which are configured. At the end,
226 #what's left in existing is the set of loggers
227 #which were in the previous configuration but
228 #which are not in the new configuration.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000229 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +0000230 #The list needs to be sorted so that we can
231 #avoid disabling child loggers of explicitly
232 #named loggers. With a sorted list it is easier
233 #to find the child loggers.
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000234 existing.sort(key=_encoded)
Christian Heimes96f31632007-11-12 01:32:03 +0000235 #We'll keep the list of existing loggers
236 #which are children of named loggers here...
237 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000238 #now set up the new ones...
239 for log in llist:
240 sectname = "logger_%s" % log
241 qn = cp.get(sectname, "qualname")
242 opts = cp.options(sectname)
243 if "propagate" in opts:
244 propagate = cp.getint(sectname, "propagate")
245 else:
246 propagate = 1
247 logger = logging.getLogger(qn)
248 if qn in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000249 i = existing.index(qn)
250 prefixed = qn + "."
251 pflen = len(prefixed)
252 num_existing = len(existing)
253 i = i + 1 # look at the entry after qn
254 while (i < num_existing) and (existing[i][:pflen] == prefixed):
255 child_loggers.append(existing[i])
256 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000257 existing.remove(qn)
258 if "level" in opts:
259 level = cp.get(sectname, "level")
260 logger.setLevel(logging._levelNames[level])
261 for h in logger.handlers[:]:
262 logger.removeHandler(h)
263 logger.propagate = propagate
264 logger.disabled = 0
265 hlist = cp.get(sectname, "handlers")
266 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000267 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000268 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000269 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000270 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000271
272 #Disable any old loggers. There's no point deleting
273 #them as other threads may continue to hold references
274 #and by disabling them, you stop them doing any logging.
Christian Heimes96f31632007-11-12 01:32:03 +0000275 #However, don't disable children of named loggers, as that's
276 #probably not what was intended by the user.
Vinay Sajipec1cd1c2010-08-30 19:02:14 +0000277 #for log in existing:
278 # logger = root.manager.loggerDict[log]
279 # if log in child_loggers:
280 # logger.level = logging.NOTSET
281 # logger.handlers = []
282 # logger.propagate = 1
283 # elif disable_existing_loggers:
284 # logger.disabled = 1
285 _handle_existing_loggers(existing, child_loggers, disable_existing)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000286
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000287IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
288
289
290def valid_ident(s):
291 m = IDENTIFIER.match(s)
292 if not m:
293 raise ValueError('Not a valid Python identifier: %r' % s)
294 return True
295
296
297# The ConvertingXXX classes are wrappers around standard Python containers,
298# and they serve to convert any suitable values in the container. The
299# conversion converts base dicts, lists and tuples to their wrapped
300# equivalents, whereas strings which match a conversion format are converted
301# appropriately.
302#
303# Each wrapper should have a configurator attribute holding the actual
304# configurator to use for conversion.
305
306class ConvertingDict(dict):
307 """A converting dictionary wrapper."""
308
309 def __getitem__(self, key):
310 value = dict.__getitem__(self, key)
311 result = self.configurator.convert(value)
312 #If the converted value is different, save for next time
313 if value is not result:
314 self[key] = result
315 if type(result) in (ConvertingDict, ConvertingList,
316 ConvertingTuple):
317 result.parent = self
318 result.key = key
319 return result
320
321 def get(self, key, default=None):
322 value = dict.get(self, key, default)
323 result = self.configurator.convert(value)
324 #If the converted value is different, save for next time
325 if value is not result:
326 self[key] = result
327 if type(result) in (ConvertingDict, ConvertingList,
328 ConvertingTuple):
329 result.parent = self
330 result.key = key
331 return result
332
333 def pop(self, key, default=None):
334 value = dict.pop(self, key, default)
335 result = self.configurator.convert(value)
336 if value is not result:
337 if type(result) in (ConvertingDict, ConvertingList,
338 ConvertingTuple):
339 result.parent = self
340 result.key = key
341 return result
342
343class ConvertingList(list):
344 """A converting list wrapper."""
345 def __getitem__(self, key):
346 value = list.__getitem__(self, key)
347 result = self.configurator.convert(value)
348 #If the converted value is different, save for next time
349 if value is not result:
350 self[key] = result
351 if type(result) in (ConvertingDict, ConvertingList,
352 ConvertingTuple):
353 result.parent = self
354 result.key = key
355 return result
356
357 def pop(self, idx=-1):
358 value = list.pop(self, idx)
359 result = self.configurator.convert(value)
360 if value is not result:
361 if type(result) in (ConvertingDict, ConvertingList,
362 ConvertingTuple):
363 result.parent = self
364 return result
365
366class ConvertingTuple(tuple):
367 """A converting tuple wrapper."""
368 def __getitem__(self, key):
369 value = tuple.__getitem__(self, key)
370 result = self.configurator.convert(value)
371 if value is not result:
372 if type(result) in (ConvertingDict, ConvertingList,
373 ConvertingTuple):
374 result.parent = self
375 result.key = key
376 return result
377
378class BaseConfigurator(object):
379 """
380 The configurator base class which defines some useful defaults.
381 """
382
383 CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
384
385 WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
386 DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
387 INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
388 DIGIT_PATTERN = re.compile(r'^\d+$')
389
390 value_converters = {
391 'ext' : 'ext_convert',
392 'cfg' : 'cfg_convert',
393 }
394
395 # We might want to use a different one, e.g. importlib
Brett Cannonc2368502010-06-12 00:39:28 +0000396 importer = staticmethod(__import__)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000397
398 def __init__(self, config):
399 self.config = ConvertingDict(config)
400 self.config.configurator = self
401
402 def resolve(self, s):
403 """
404 Resolve strings to objects using standard import and attribute
405 syntax.
406 """
407 name = s.split('.')
408 used = name.pop(0)
Benjamin Petersona82addb2010-06-27 20:54:28 +0000409 try:
410 found = self.importer(used)
411 for frag in name:
412 used += '.' + frag
413 try:
414 found = getattr(found, frag)
415 except AttributeError:
416 self.importer(used)
417 found = getattr(found, frag)
418 return found
419 except ImportError:
420 e, tb = sys.exc_info()[1:]
421 v = ValueError('Cannot resolve %r: %s' % (s, e))
422 v.__cause__, v.__traceback__ = e, tb
423 raise v
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000424
425 def ext_convert(self, value):
426 """Default converter for the ext:// protocol."""
427 return self.resolve(value)
428
429 def cfg_convert(self, value):
430 """Default converter for the cfg:// protocol."""
431 rest = value
432 m = self.WORD_PATTERN.match(rest)
433 if m is None:
434 raise ValueError("Unable to convert %r" % value)
435 else:
436 rest = rest[m.end():]
437 d = self.config[m.groups()[0]]
438 #print d, rest
439 while rest:
440 m = self.DOT_PATTERN.match(rest)
441 if m:
442 d = d[m.groups()[0]]
443 else:
444 m = self.INDEX_PATTERN.match(rest)
445 if m:
446 idx = m.groups()[0]
447 if not self.DIGIT_PATTERN.match(idx):
448 d = d[idx]
449 else:
450 try:
451 n = int(idx) # try as number first (most likely)
452 d = d[n]
453 except TypeError:
454 d = d[idx]
455 if m:
456 rest = rest[m.end():]
457 else:
458 raise ValueError('Unable to convert '
459 '%r at %r' % (value, rest))
460 #rest should be empty
461 return d
462
463 def convert(self, value):
464 """
465 Convert values to an appropriate type. dicts, lists and tuples are
466 replaced by their converting alternatives. Strings are checked to
467 see if they have a conversion format and are converted if they do.
468 """
469 if not isinstance(value, ConvertingDict) and isinstance(value, dict):
470 value = ConvertingDict(value)
471 value.configurator = self
472 elif not isinstance(value, ConvertingList) and isinstance(value, list):
473 value = ConvertingList(value)
474 value.configurator = self
475 elif not isinstance(value, ConvertingTuple) and\
476 isinstance(value, tuple):
477 value = ConvertingTuple(value)
478 value.configurator = self
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000479 elif isinstance(value, str): # str for py3k
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000480 m = self.CONVERT_PATTERN.match(value)
481 if m:
482 d = m.groupdict()
483 prefix = d['prefix']
484 converter = self.value_converters.get(prefix, None)
485 if converter:
486 suffix = d['suffix']
487 converter = getattr(self, converter)
488 value = converter(suffix)
489 return value
490
491 def configure_custom(self, config):
492 """Configure an object with a user-supplied factory."""
493 c = config.pop('()')
494 if not hasattr(c, '__call__'):
495 c = self.resolve(c)
496 props = config.pop('.', None)
497 # Check for valid identifiers
498 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
499 result = c(**kwargs)
500 if props:
501 for name, value in props.items():
502 setattr(result, name, value)
503 return result
504
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000505 def as_tuple(self, value):
506 """Utility function which converts lists to tuples."""
507 if isinstance(value, list):
508 value = tuple(value)
509 return value
510
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000511class DictConfigurator(BaseConfigurator):
512 """
513 Configure logging using a dictionary-like object to describe the
514 configuration.
515 """
516
517 def configure(self):
518 """Do the configuration."""
519
520 config = self.config
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000521 if 'version' not in config:
522 raise ValueError("dictionary doesn't specify a version")
523 if config['version'] != 1:
524 raise ValueError("Unsupported version: %s" % config['version'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000525 incremental = config.pop('incremental', False)
526 EMPTY_DICT = {}
527 logging._acquireLock()
528 try:
529 if incremental:
530 handlers = config.get('handlers', EMPTY_DICT)
531 for name in handlers:
532 if name not in logging._handlers:
533 raise ValueError('No handler found with '
534 'name %r' % name)
535 else:
536 try:
537 handler = logging._handlers[name]
538 handler_config = handlers[name]
539 level = handler_config.get('level', None)
540 if level:
541 handler.setLevel(logging._checkLevel(level))
542 except Exception as e:
543 raise ValueError('Unable to configure handler '
544 '%r: %s' % (name, e))
545 loggers = config.get('loggers', EMPTY_DICT)
546 for name in loggers:
547 try:
548 self.configure_logger(name, loggers[name], True)
549 except Exception as e:
550 raise ValueError('Unable to configure logger '
551 '%r: %s' % (name, e))
552 root = config.get('root', None)
553 if root:
554 try:
555 self.configure_root(root, True)
556 except Exception as e:
557 raise ValueError('Unable to configure root '
558 'logger: %s' % e)
559 else:
560 disable_existing = config.pop('disable_existing_loggers', True)
561
562 logging._handlers.clear()
563 del logging._handlerList[:]
564
565 # Do formatters first - they don't refer to anything else
566 formatters = config.get('formatters', EMPTY_DICT)
567 for name in formatters:
568 try:
569 formatters[name] = self.configure_formatter(
570 formatters[name])
571 except Exception as e:
572 raise ValueError('Unable to configure '
573 'formatter %r: %s' % (name, e))
574 # Next, do filters - they don't refer to anything else, either
575 filters = config.get('filters', EMPTY_DICT)
576 for name in filters:
577 try:
578 filters[name] = self.configure_filter(filters[name])
579 except Exception as e:
580 raise ValueError('Unable to configure '
581 'filter %r: %s' % (name, e))
582
583 # Next, do handlers - they refer to formatters and filters
584 # As handlers can refer to other handlers, sort the keys
585 # to allow a deterministic order of configuration
586 handlers = config.get('handlers', EMPTY_DICT)
587 for name in sorted(handlers):
588 try:
589 handler = self.configure_handler(handlers[name])
590 handler.name = name
591 handlers[name] = handler
592 except Exception as e:
593 raise ValueError('Unable to configure handler '
594 '%r: %s' % (name, e))
595 # Next, do loggers - they refer to handlers and filters
596
597 #we don't want to lose the existing loggers,
598 #since other threads may have pointers to them.
599 #existing is set to contain all existing loggers,
600 #and as we go through the new configuration we
601 #remove any which are configured. At the end,
602 #what's left in existing is the set of loggers
603 #which were in the previous configuration but
604 #which are not in the new configuration.
605 root = logging.root
606 existing = list(root.manager.loggerDict.keys())
607 #The list needs to be sorted so that we can
608 #avoid disabling child loggers of explicitly
609 #named loggers. With a sorted list it is easier
610 #to find the child loggers.
Benjamin Peterson22005fc2010-04-11 16:25:06 +0000611 existing.sort(key=_encoded)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000612 #We'll keep the list of existing loggers
613 #which are children of named loggers here...
614 child_loggers = []
615 #now set up the new ones...
616 loggers = config.get('loggers', EMPTY_DICT)
617 for name in loggers:
618 if name in existing:
619 i = existing.index(name)
620 prefixed = name + "."
621 pflen = len(prefixed)
622 num_existing = len(existing)
623 i = i + 1 # look at the entry after name
624 while (i < num_existing) and\
625 (existing[i][:pflen] == prefixed):
626 child_loggers.append(existing[i])
627 i = i + 1
628 existing.remove(name)
629 try:
630 self.configure_logger(name, loggers[name])
631 except Exception as e:
632 raise ValueError('Unable to configure logger '
633 '%r: %s' % (name, e))
634
635 #Disable any old loggers. There's no point deleting
636 #them as other threads may continue to hold references
637 #and by disabling them, you stop them doing any logging.
638 #However, don't disable children of named loggers, as that's
639 #probably not what was intended by the user.
Vinay Sajipec1cd1c2010-08-30 19:02:14 +0000640 #for log in existing:
641 # logger = root.manager.loggerDict[log]
642 # if log in child_loggers:
643 # logger.level = logging.NOTSET
644 # logger.handlers = []
645 # logger.propagate = True
646 # elif disable_existing:
647 # logger.disabled = True
648 _handle_existing_loggers(existing, child_loggers,
649 disable_existing)
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000650
651 # And finally, do the root logger
652 root = config.get('root', None)
653 if root:
654 try:
655 self.configure_root(root)
656 except Exception as e:
657 raise ValueError('Unable to configure root '
658 'logger: %s' % e)
659 finally:
660 logging._releaseLock()
661
662 def configure_formatter(self, config):
663 """Configure a formatter from a dictionary."""
664 if '()' in config:
665 factory = config['()'] # for use in exception handler
666 try:
667 result = self.configure_custom(config)
668 except TypeError as te:
669 if "'format'" not in str(te):
670 raise
671 #Name of parameter changed from fmt to format.
672 #Retry with old name.
673 #This is so that code can be used with older Python versions
674 #(e.g. by Django)
675 config['fmt'] = config.pop('format')
676 config['()'] = factory
677 result = self.configure_custom(config)
678 else:
679 fmt = config.get('format', None)
680 dfmt = config.get('datefmt', None)
681 result = logging.Formatter(fmt, dfmt)
682 return result
683
684 def configure_filter(self, config):
685 """Configure a filter from a dictionary."""
686 if '()' in config:
687 result = self.configure_custom(config)
688 else:
689 name = config.get('name', '')
690 result = logging.Filter(name)
691 return result
692
693 def add_filters(self, filterer, filters):
694 """Add filters to a filterer from a list of names."""
695 for f in filters:
696 try:
697 filterer.addFilter(self.config['filters'][f])
698 except Exception as e:
699 raise ValueError('Unable to add filter %r: %s' % (f, e))
700
701 def configure_handler(self, config):
702 """Configure a handler from a dictionary."""
703 formatter = config.pop('formatter', None)
704 if formatter:
705 try:
706 formatter = self.config['formatters'][formatter]
707 except Exception as e:
708 raise ValueError('Unable to set formatter '
709 '%r: %s' % (formatter, e))
710 level = config.pop('level', None)
711 filters = config.pop('filters', None)
712 if '()' in config:
713 c = config.pop('()')
714 if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
715 c = self.resolve(c)
716 factory = c
717 else:
718 klass = self.resolve(config.pop('class'))
719 #Special case for handler which refers to another handler
720 if issubclass(klass, logging.handlers.MemoryHandler) and\
721 'target' in config:
722 try:
723 config['target'] = self.config['handlers'][config['target']]
724 except Exception as e:
725 raise ValueError('Unable to set target handler '
726 '%r: %s' % (config['target'], e))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000727 elif issubclass(klass, logging.handlers.SMTPHandler) and\
728 'mailhost' in config:
729 config['mailhost'] = self.as_tuple(config['mailhost'])
730 elif issubclass(klass, logging.handlers.SysLogHandler) and\
731 'address' in config:
732 config['address'] = self.as_tuple(config['address'])
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000733 factory = klass
734 kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
735 try:
736 result = factory(**kwargs)
737 except TypeError as te:
738 if "'stream'" not in str(te):
739 raise
740 #The argument name changed from strm to stream
741 #Retry with old name.
742 #This is so that code can be used with older Python versions
743 #(e.g. by Django)
744 kwargs['strm'] = kwargs.pop('stream')
745 result = factory(**kwargs)
746 if formatter:
747 result.setFormatter(formatter)
748 if level is not None:
749 result.setLevel(logging._checkLevel(level))
750 if filters:
751 self.add_filters(result, filters)
752 return result
753
754 def add_handlers(self, logger, handlers):
755 """Add handlers to a logger from a list of names."""
756 for h in handlers:
757 try:
758 logger.addHandler(self.config['handlers'][h])
759 except Exception as e:
760 raise ValueError('Unable to add handler %r: %s' % (h, e))
761
762 def common_logger_config(self, logger, config, incremental=False):
763 """
764 Perform configuration which is common to root and non-root loggers.
765 """
766 level = config.get('level', None)
767 if level is not None:
768 logger.setLevel(logging._checkLevel(level))
769 if not incremental:
770 #Remove any existing handlers
771 for h in logger.handlers[:]:
772 logger.removeHandler(h)
773 handlers = config.get('handlers', None)
774 if handlers:
775 self.add_handlers(logger, handlers)
776 filters = config.get('filters', None)
777 if filters:
778 self.add_filters(logger, filters)
779
780 def configure_logger(self, name, config, incremental=False):
781 """Configure a non-root logger from a dictionary."""
782 logger = logging.getLogger(name)
783 self.common_logger_config(logger, config, incremental)
784 propagate = config.get('propagate', None)
785 if propagate is not None:
786 logger.propagate = propagate
787
788 def configure_root(self, config, incremental=False):
789 """Configure a root logger from a dictionary."""
790 root = logging.getLogger()
791 self.common_logger_config(root, config, incremental)
792
793dictConfigClass = DictConfigurator
794
795def dictConfig(config):
796 """Configure logging using a dictionary."""
797 dictConfigClass(config).configure()
798
799
Guido van Rossum57102f82002-11-13 16:15:58 +0000800def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
801 """
802 Start up a socket server on the specified port, and listen for new
803 configurations.
804
805 These will be sent as a file suitable for processing by fileConfig().
806 Returns a Thread object on which you can call start() to start the server,
807 and which you can join() when appropriate. To stop the server, call
808 stopListening().
809 """
810 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000811 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000812
813 class ConfigStreamHandler(StreamRequestHandler):
814 """
815 Handler for a logging configuration request.
816
817 It expects a completely new logging configuration and uses fileConfig
818 to install it.
819 """
820 def handle(self):
821 """
822 Handle a request.
823
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000824 Each request is expected to be a 4-byte length, packed using
825 struct.pack(">L", n), followed by the config file.
826 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000827 """
828 import tempfile
829 try:
830 conn = self.connection
831 chunk = conn.recv(4)
832 if len(chunk) == 4:
833 slen = struct.unpack(">L", chunk)[0]
834 chunk = self.connection.recv(slen)
835 while len(chunk) < slen:
836 chunk = chunk + conn.recv(slen - len(chunk))
Benjamin Peterson9451a1c2010-03-13 22:30:34 +0000837 chunk = chunk.decode("utf-8")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000838 try:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000839 import json
840 d =json.loads(chunk)
841 assert isinstance(d, dict)
842 dictConfig(d)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000843 except:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000844 #Apply new configuration.
845
846 file = io.StringIO(chunk)
847 try:
848 fileConfig(file)
849 except (KeyboardInterrupt, SystemExit):
850 raise
851 except:
852 traceback.print_exc()
853 if self.server.ready:
854 self.server.ready.set()
Guido van Rossumb940e112007-01-10 16:19:56 +0000855 except socket.error as e:
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000856 if not isinstance(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000857 raise
858 else:
859 errcode = e.args[0]
860 if errcode != RESET_ERROR:
861 raise
862
863 class ConfigSocketReceiver(ThreadingTCPServer):
864 """
865 A simple TCP socket-based logging config receiver.
866 """
867
868 allow_reuse_address = 1
869
870 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000871 handler=None, ready=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000872 ThreadingTCPServer.__init__(self, (host, port), handler)
873 logging._acquireLock()
874 self.abort = 0
875 logging._releaseLock()
876 self.timeout = 1
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000877 self.ready = ready
Guido van Rossum57102f82002-11-13 16:15:58 +0000878
879 def serve_until_stopped(self):
880 import select
881 abort = 0
882 while not abort:
883 rd, wr, ex = select.select([self.socket.fileno()],
884 [], [],
885 self.timeout)
886 if rd:
887 self.handle_request()
888 logging._acquireLock()
889 abort = self.abort
890 logging._releaseLock()
891
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000892 class Server(threading.Thread):
Guido van Rossum57102f82002-11-13 16:15:58 +0000893
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000894 def __init__(self, rcvr, hdlr, port):
895 super(Server, self).__init__()
896 self.rcvr = rcvr
897 self.hdlr = hdlr
898 self.port = port
899 self.ready = threading.Event()
900
901 def run(self):
902 server = self.rcvr(port=self.port, handler=self.hdlr,
903 ready=self.ready)
Benjamin Petersona82addb2010-06-27 20:54:28 +0000904 if self.port == 0:
905 self.port = server.server_address[1]
Vinay Sajipdb81c4c2010-02-25 23:13:06 +0000906 self.ready.set()
907 global _listener
908 logging._acquireLock()
909 _listener = server
910 logging._releaseLock()
911 server.serve_until_stopped()
912
913 return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
Guido van Rossum57102f82002-11-13 16:15:58 +0000914
915def stopListening():
916 """
917 Stop the listening server which was created with a call to listen().
918 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000919 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000920 if _listener:
921 logging._acquireLock()
922 _listener.abort = 1
923 _listener = None
924 logging._releaseLock()