]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | # Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.\r |
2 | #\r | |
3 | # Permission to use, copy, modify, and distribute this software and its\r | |
4 | # documentation for any purpose and without fee is hereby granted,\r | |
5 | # provided that the above copyright notice appear in all copies and that\r | |
6 | # both that copyright notice and this permission notice appear in\r | |
7 | # supporting documentation, and that the name of Vinay Sajip\r | |
8 | # not be used in advertising or publicity pertaining to distribution\r | |
9 | # of the software without specific, written prior permission.\r | |
10 | # VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r | |
11 | # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL\r | |
12 | # VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r | |
13 | # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER\r | |
14 | # IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT\r | |
15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\r | |
16 | \r | |
17 | """\r | |
18 | Configuration functions for the logging package for Python. The core package\r | |
19 | is based on PEP 282 and comments thereto in comp.lang.python, and influenced\r | |
20 | by Apache's log4j system.\r | |
21 | \r | |
22 | Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.\r | |
23 | \r | |
24 | To use, simply 'import logging' and log away!\r | |
25 | """\r | |
26 | \r | |
27 | import sys, logging, logging.handlers, socket, struct, os, traceback, re\r | |
28 | import types, cStringIO\r | |
29 | \r | |
30 | try:\r | |
31 | import thread\r | |
32 | import threading\r | |
33 | except ImportError:\r | |
34 | thread = None\r | |
35 | \r | |
36 | from SocketServer import ThreadingTCPServer, StreamRequestHandler\r | |
37 | \r | |
38 | \r | |
39 | DEFAULT_LOGGING_CONFIG_PORT = 9030\r | |
40 | \r | |
41 | if sys.platform == "win32":\r | |
42 | RESET_ERROR = 10054 #WSAECONNRESET\r | |
43 | else:\r | |
44 | RESET_ERROR = 104 #ECONNRESET\r | |
45 | \r | |
46 | #\r | |
47 | # The following code implements a socket listener for on-the-fly\r | |
48 | # reconfiguration of logging.\r | |
49 | #\r | |
50 | # _listener holds the server object doing the listening\r | |
51 | _listener = None\r | |
52 | \r | |
53 | def fileConfig(fname, defaults=None, disable_existing_loggers=True):\r | |
54 | """\r | |
55 | Read the logging configuration from a ConfigParser-format file.\r | |
56 | \r | |
57 | This can be called several times from an application, allowing an end user\r | |
58 | the ability to select from various pre-canned configurations (if the\r | |
59 | developer provides a mechanism to present the choices and load the chosen\r | |
60 | configuration).\r | |
61 | """\r | |
62 | import ConfigParser\r | |
63 | \r | |
64 | cp = ConfigParser.ConfigParser(defaults)\r | |
65 | if hasattr(fname, 'readline'):\r | |
66 | cp.readfp(fname)\r | |
67 | else:\r | |
68 | cp.read(fname)\r | |
69 | \r | |
70 | formatters = _create_formatters(cp)\r | |
71 | \r | |
72 | # critical section\r | |
73 | logging._acquireLock()\r | |
74 | try:\r | |
75 | logging._handlers.clear()\r | |
76 | del logging._handlerList[:]\r | |
77 | # Handlers add themselves to logging._handlers\r | |
78 | handlers = _install_handlers(cp, formatters)\r | |
79 | _install_loggers(cp, handlers, disable_existing_loggers)\r | |
80 | finally:\r | |
81 | logging._releaseLock()\r | |
82 | \r | |
83 | \r | |
84 | def _resolve(name):\r | |
85 | """Resolve a dotted name to a global object."""\r | |
86 | name = name.split('.')\r | |
87 | used = name.pop(0)\r | |
88 | found = __import__(used)\r | |
89 | for n in name:\r | |
90 | used = used + '.' + n\r | |
91 | try:\r | |
92 | found = getattr(found, n)\r | |
93 | except AttributeError:\r | |
94 | __import__(used)\r | |
95 | found = getattr(found, n)\r | |
96 | return found\r | |
97 | \r | |
98 | def _strip_spaces(alist):\r | |
99 | return map(lambda x: x.strip(), alist)\r | |
100 | \r | |
101 | def _encoded(s):\r | |
102 | return s if isinstance(s, str) else s.encode('utf-8')\r | |
103 | \r | |
104 | def _create_formatters(cp):\r | |
105 | """Create and return formatters"""\r | |
106 | flist = cp.get("formatters", "keys")\r | |
107 | if not len(flist):\r | |
108 | return {}\r | |
109 | flist = flist.split(",")\r | |
110 | flist = _strip_spaces(flist)\r | |
111 | formatters = {}\r | |
112 | for form in flist:\r | |
113 | sectname = "formatter_%s" % form\r | |
114 | opts = cp.options(sectname)\r | |
115 | if "format" in opts:\r | |
116 | fs = cp.get(sectname, "format", 1)\r | |
117 | else:\r | |
118 | fs = None\r | |
119 | if "datefmt" in opts:\r | |
120 | dfs = cp.get(sectname, "datefmt", 1)\r | |
121 | else:\r | |
122 | dfs = None\r | |
123 | c = logging.Formatter\r | |
124 | if "class" in opts:\r | |
125 | class_name = cp.get(sectname, "class")\r | |
126 | if class_name:\r | |
127 | c = _resolve(class_name)\r | |
128 | f = c(fs, dfs)\r | |
129 | formatters[form] = f\r | |
130 | return formatters\r | |
131 | \r | |
132 | \r | |
133 | def _install_handlers(cp, formatters):\r | |
134 | """Install and return handlers"""\r | |
135 | hlist = cp.get("handlers", "keys")\r | |
136 | if not len(hlist):\r | |
137 | return {}\r | |
138 | hlist = hlist.split(",")\r | |
139 | hlist = _strip_spaces(hlist)\r | |
140 | handlers = {}\r | |
141 | fixups = [] #for inter-handler references\r | |
142 | for hand in hlist:\r | |
143 | sectname = "handler_%s" % hand\r | |
144 | klass = cp.get(sectname, "class")\r | |
145 | opts = cp.options(sectname)\r | |
146 | if "formatter" in opts:\r | |
147 | fmt = cp.get(sectname, "formatter")\r | |
148 | else:\r | |
149 | fmt = ""\r | |
150 | try:\r | |
151 | klass = eval(klass, vars(logging))\r | |
152 | except (AttributeError, NameError):\r | |
153 | klass = _resolve(klass)\r | |
154 | args = cp.get(sectname, "args")\r | |
155 | args = eval(args, vars(logging))\r | |
156 | h = klass(*args)\r | |
157 | if "level" in opts:\r | |
158 | level = cp.get(sectname, "level")\r | |
159 | h.setLevel(logging._levelNames[level])\r | |
160 | if len(fmt):\r | |
161 | h.setFormatter(formatters[fmt])\r | |
162 | if issubclass(klass, logging.handlers.MemoryHandler):\r | |
163 | if "target" in opts:\r | |
164 | target = cp.get(sectname,"target")\r | |
165 | else:\r | |
166 | target = ""\r | |
167 | if len(target): #the target handler may not be loaded yet, so keep for later...\r | |
168 | fixups.append((h, target))\r | |
169 | handlers[hand] = h\r | |
170 | #now all handlers are loaded, fixup inter-handler references...\r | |
171 | for h, t in fixups:\r | |
172 | h.setTarget(handlers[t])\r | |
173 | return handlers\r | |
174 | \r | |
175 | \r | |
176 | def _install_loggers(cp, handlers, disable_existing_loggers):\r | |
177 | """Create and install loggers"""\r | |
178 | \r | |
179 | # configure the root first\r | |
180 | llist = cp.get("loggers", "keys")\r | |
181 | llist = llist.split(",")\r | |
182 | llist = list(map(lambda x: x.strip(), llist))\r | |
183 | llist.remove("root")\r | |
184 | sectname = "logger_root"\r | |
185 | root = logging.root\r | |
186 | log = root\r | |
187 | opts = cp.options(sectname)\r | |
188 | if "level" in opts:\r | |
189 | level = cp.get(sectname, "level")\r | |
190 | log.setLevel(logging._levelNames[level])\r | |
191 | for h in root.handlers[:]:\r | |
192 | root.removeHandler(h)\r | |
193 | hlist = cp.get(sectname, "handlers")\r | |
194 | if len(hlist):\r | |
195 | hlist = hlist.split(",")\r | |
196 | hlist = _strip_spaces(hlist)\r | |
197 | for hand in hlist:\r | |
198 | log.addHandler(handlers[hand])\r | |
199 | \r | |
200 | #and now the others...\r | |
201 | #we don't want to lose the existing loggers,\r | |
202 | #since other threads may have pointers to them.\r | |
203 | #existing is set to contain all existing loggers,\r | |
204 | #and as we go through the new configuration we\r | |
205 | #remove any which are configured. At the end,\r | |
206 | #what's left in existing is the set of loggers\r | |
207 | #which were in the previous configuration but\r | |
208 | #which are not in the new configuration.\r | |
209 | existing = list(root.manager.loggerDict.keys())\r | |
210 | #The list needs to be sorted so that we can\r | |
211 | #avoid disabling child loggers of explicitly\r | |
212 | #named loggers. With a sorted list it is easier\r | |
213 | #to find the child loggers.\r | |
214 | existing.sort(key=_encoded)\r | |
215 | #We'll keep the list of existing loggers\r | |
216 | #which are children of named loggers here...\r | |
217 | child_loggers = []\r | |
218 | #now set up the new ones...\r | |
219 | for log in llist:\r | |
220 | sectname = "logger_%s" % log\r | |
221 | qn = cp.get(sectname, "qualname")\r | |
222 | opts = cp.options(sectname)\r | |
223 | if "propagate" in opts:\r | |
224 | propagate = cp.getint(sectname, "propagate")\r | |
225 | else:\r | |
226 | propagate = 1\r | |
227 | logger = logging.getLogger(qn)\r | |
228 | if qn in existing:\r | |
229 | i = existing.index(qn) + 1 # start with the entry after qn\r | |
230 | prefixed = qn + "."\r | |
231 | pflen = len(prefixed)\r | |
232 | num_existing = len(existing)\r | |
233 | while i < num_existing:\r | |
234 | if existing[i][:pflen] == prefixed:\r | |
235 | child_loggers.append(existing[i])\r | |
236 | i += 1\r | |
237 | existing.remove(qn)\r | |
238 | if "level" in opts:\r | |
239 | level = cp.get(sectname, "level")\r | |
240 | logger.setLevel(logging._levelNames[level])\r | |
241 | for h in logger.handlers[:]:\r | |
242 | logger.removeHandler(h)\r | |
243 | logger.propagate = propagate\r | |
244 | logger.disabled = 0\r | |
245 | hlist = cp.get(sectname, "handlers")\r | |
246 | if len(hlist):\r | |
247 | hlist = hlist.split(",")\r | |
248 | hlist = _strip_spaces(hlist)\r | |
249 | for hand in hlist:\r | |
250 | logger.addHandler(handlers[hand])\r | |
251 | \r | |
252 | #Disable any old loggers. There's no point deleting\r | |
253 | #them as other threads may continue to hold references\r | |
254 | #and by disabling them, you stop them doing any logging.\r | |
255 | #However, don't disable children of named loggers, as that's\r | |
256 | #probably not what was intended by the user.\r | |
257 | for log in existing:\r | |
258 | logger = root.manager.loggerDict[log]\r | |
259 | if log in child_loggers:\r | |
260 | logger.level = logging.NOTSET\r | |
261 | logger.handlers = []\r | |
262 | logger.propagate = 1\r | |
263 | elif disable_existing_loggers:\r | |
264 | logger.disabled = 1\r | |
265 | \r | |
266 | \r | |
267 | \r | |
268 | IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)\r | |
269 | \r | |
270 | \r | |
271 | def valid_ident(s):\r | |
272 | m = IDENTIFIER.match(s)\r | |
273 | if not m:\r | |
274 | raise ValueError('Not a valid Python identifier: %r' % s)\r | |
275 | return True\r | |
276 | \r | |
277 | \r | |
278 | # The ConvertingXXX classes are wrappers around standard Python containers,\r | |
279 | # and they serve to convert any suitable values in the container. The\r | |
280 | # conversion converts base dicts, lists and tuples to their wrapped\r | |
281 | # equivalents, whereas strings which match a conversion format are converted\r | |
282 | # appropriately.\r | |
283 | #\r | |
284 | # Each wrapper should have a configurator attribute holding the actual\r | |
285 | # configurator to use for conversion.\r | |
286 | \r | |
287 | class ConvertingDict(dict):\r | |
288 | """A converting dictionary wrapper."""\r | |
289 | \r | |
290 | def __getitem__(self, key):\r | |
291 | value = dict.__getitem__(self, key)\r | |
292 | result = self.configurator.convert(value)\r | |
293 | #If the converted value is different, save for next time\r | |
294 | if value is not result:\r | |
295 | self[key] = result\r | |
296 | if type(result) in (ConvertingDict, ConvertingList,\r | |
297 | ConvertingTuple):\r | |
298 | result.parent = self\r | |
299 | result.key = key\r | |
300 | return result\r | |
301 | \r | |
302 | def get(self, key, default=None):\r | |
303 | value = dict.get(self, key, default)\r | |
304 | result = self.configurator.convert(value)\r | |
305 | #If the converted value is different, save for next time\r | |
306 | if value is not result:\r | |
307 | self[key] = result\r | |
308 | if type(result) in (ConvertingDict, ConvertingList,\r | |
309 | ConvertingTuple):\r | |
310 | result.parent = self\r | |
311 | result.key = key\r | |
312 | return result\r | |
313 | \r | |
314 | def pop(self, key, default=None):\r | |
315 | value = dict.pop(self, key, default)\r | |
316 | result = self.configurator.convert(value)\r | |
317 | if value is not result:\r | |
318 | if type(result) in (ConvertingDict, ConvertingList,\r | |
319 | ConvertingTuple):\r | |
320 | result.parent = self\r | |
321 | result.key = key\r | |
322 | return result\r | |
323 | \r | |
324 | class ConvertingList(list):\r | |
325 | """A converting list wrapper."""\r | |
326 | def __getitem__(self, key):\r | |
327 | value = list.__getitem__(self, key)\r | |
328 | result = self.configurator.convert(value)\r | |
329 | #If the converted value is different, save for next time\r | |
330 | if value is not result:\r | |
331 | self[key] = result\r | |
332 | if type(result) in (ConvertingDict, ConvertingList,\r | |
333 | ConvertingTuple):\r | |
334 | result.parent = self\r | |
335 | result.key = key\r | |
336 | return result\r | |
337 | \r | |
338 | def pop(self, idx=-1):\r | |
339 | value = list.pop(self, idx)\r | |
340 | result = self.configurator.convert(value)\r | |
341 | if value is not result:\r | |
342 | if type(result) in (ConvertingDict, ConvertingList,\r | |
343 | ConvertingTuple):\r | |
344 | result.parent = self\r | |
345 | return result\r | |
346 | \r | |
347 | class ConvertingTuple(tuple):\r | |
348 | """A converting tuple wrapper."""\r | |
349 | def __getitem__(self, key):\r | |
350 | value = tuple.__getitem__(self, key)\r | |
351 | result = self.configurator.convert(value)\r | |
352 | if value is not result:\r | |
353 | if type(result) in (ConvertingDict, ConvertingList,\r | |
354 | ConvertingTuple):\r | |
355 | result.parent = self\r | |
356 | result.key = key\r | |
357 | return result\r | |
358 | \r | |
359 | class BaseConfigurator(object):\r | |
360 | """\r | |
361 | The configurator base class which defines some useful defaults.\r | |
362 | """\r | |
363 | \r | |
364 | CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')\r | |
365 | \r | |
366 | WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')\r | |
367 | DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')\r | |
368 | INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')\r | |
369 | DIGIT_PATTERN = re.compile(r'^\d+$')\r | |
370 | \r | |
371 | value_converters = {\r | |
372 | 'ext' : 'ext_convert',\r | |
373 | 'cfg' : 'cfg_convert',\r | |
374 | }\r | |
375 | \r | |
376 | # We might want to use a different one, e.g. importlib\r | |
377 | importer = __import__\r | |
378 | \r | |
379 | def __init__(self, config):\r | |
380 | self.config = ConvertingDict(config)\r | |
381 | self.config.configurator = self\r | |
382 | \r | |
383 | def resolve(self, s):\r | |
384 | """\r | |
385 | Resolve strings to objects using standard import and attribute\r | |
386 | syntax.\r | |
387 | """\r | |
388 | name = s.split('.')\r | |
389 | used = name.pop(0)\r | |
390 | try:\r | |
391 | found = self.importer(used)\r | |
392 | for frag in name:\r | |
393 | used += '.' + frag\r | |
394 | try:\r | |
395 | found = getattr(found, frag)\r | |
396 | except AttributeError:\r | |
397 | self.importer(used)\r | |
398 | found = getattr(found, frag)\r | |
399 | return found\r | |
400 | except ImportError:\r | |
401 | e, tb = sys.exc_info()[1:]\r | |
402 | v = ValueError('Cannot resolve %r: %s' % (s, e))\r | |
403 | v.__cause__, v.__traceback__ = e, tb\r | |
404 | raise v\r | |
405 | \r | |
406 | def ext_convert(self, value):\r | |
407 | """Default converter for the ext:// protocol."""\r | |
408 | return self.resolve(value)\r | |
409 | \r | |
410 | def cfg_convert(self, value):\r | |
411 | """Default converter for the cfg:// protocol."""\r | |
412 | rest = value\r | |
413 | m = self.WORD_PATTERN.match(rest)\r | |
414 | if m is None:\r | |
415 | raise ValueError("Unable to convert %r" % value)\r | |
416 | else:\r | |
417 | rest = rest[m.end():]\r | |
418 | d = self.config[m.groups()[0]]\r | |
419 | #print d, rest\r | |
420 | while rest:\r | |
421 | m = self.DOT_PATTERN.match(rest)\r | |
422 | if m:\r | |
423 | d = d[m.groups()[0]]\r | |
424 | else:\r | |
425 | m = self.INDEX_PATTERN.match(rest)\r | |
426 | if m:\r | |
427 | idx = m.groups()[0]\r | |
428 | if not self.DIGIT_PATTERN.match(idx):\r | |
429 | d = d[idx]\r | |
430 | else:\r | |
431 | try:\r | |
432 | n = int(idx) # try as number first (most likely)\r | |
433 | d = d[n]\r | |
434 | except TypeError:\r | |
435 | d = d[idx]\r | |
436 | if m:\r | |
437 | rest = rest[m.end():]\r | |
438 | else:\r | |
439 | raise ValueError('Unable to convert '\r | |
440 | '%r at %r' % (value, rest))\r | |
441 | #rest should be empty\r | |
442 | return d\r | |
443 | \r | |
444 | def convert(self, value):\r | |
445 | """\r | |
446 | Convert values to an appropriate type. dicts, lists and tuples are\r | |
447 | replaced by their converting alternatives. Strings are checked to\r | |
448 | see if they have a conversion format and are converted if they do.\r | |
449 | """\r | |
450 | if not isinstance(value, ConvertingDict) and isinstance(value, dict):\r | |
451 | value = ConvertingDict(value)\r | |
452 | value.configurator = self\r | |
453 | elif not isinstance(value, ConvertingList) and isinstance(value, list):\r | |
454 | value = ConvertingList(value)\r | |
455 | value.configurator = self\r | |
456 | elif not isinstance(value, ConvertingTuple) and\\r | |
457 | isinstance(value, tuple):\r | |
458 | value = ConvertingTuple(value)\r | |
459 | value.configurator = self\r | |
460 | elif isinstance(value, basestring): # str for py3k\r | |
461 | m = self.CONVERT_PATTERN.match(value)\r | |
462 | if m:\r | |
463 | d = m.groupdict()\r | |
464 | prefix = d['prefix']\r | |
465 | converter = self.value_converters.get(prefix, None)\r | |
466 | if converter:\r | |
467 | suffix = d['suffix']\r | |
468 | converter = getattr(self, converter)\r | |
469 | value = converter(suffix)\r | |
470 | return value\r | |
471 | \r | |
472 | def configure_custom(self, config):\r | |
473 | """Configure an object with a user-supplied factory."""\r | |
474 | c = config.pop('()')\r | |
475 | if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:\r | |
476 | c = self.resolve(c)\r | |
477 | props = config.pop('.', None)\r | |
478 | # Check for valid identifiers\r | |
479 | kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])\r | |
480 | result = c(**kwargs)\r | |
481 | if props:\r | |
482 | for name, value in props.items():\r | |
483 | setattr(result, name, value)\r | |
484 | return result\r | |
485 | \r | |
486 | def as_tuple(self, value):\r | |
487 | """Utility function which converts lists to tuples."""\r | |
488 | if isinstance(value, list):\r | |
489 | value = tuple(value)\r | |
490 | return value\r | |
491 | \r | |
492 | class DictConfigurator(BaseConfigurator):\r | |
493 | """\r | |
494 | Configure logging using a dictionary-like object to describe the\r | |
495 | configuration.\r | |
496 | """\r | |
497 | \r | |
498 | def configure(self):\r | |
499 | """Do the configuration."""\r | |
500 | \r | |
501 | config = self.config\r | |
502 | if 'version' not in config:\r | |
503 | raise ValueError("dictionary doesn't specify a version")\r | |
504 | if config['version'] != 1:\r | |
505 | raise ValueError("Unsupported version: %s" % config['version'])\r | |
506 | incremental = config.pop('incremental', False)\r | |
507 | EMPTY_DICT = {}\r | |
508 | logging._acquireLock()\r | |
509 | try:\r | |
510 | if incremental:\r | |
511 | handlers = config.get('handlers', EMPTY_DICT)\r | |
512 | for name in handlers:\r | |
513 | if name not in logging._handlers:\r | |
514 | raise ValueError('No handler found with '\r | |
515 | 'name %r' % name)\r | |
516 | else:\r | |
517 | try:\r | |
518 | handler = logging._handlers[name]\r | |
519 | handler_config = handlers[name]\r | |
520 | level = handler_config.get('level', None)\r | |
521 | if level:\r | |
522 | handler.setLevel(logging._checkLevel(level))\r | |
523 | except StandardError, e:\r | |
524 | raise ValueError('Unable to configure handler '\r | |
525 | '%r: %s' % (name, e))\r | |
526 | loggers = config.get('loggers', EMPTY_DICT)\r | |
527 | for name in loggers:\r | |
528 | try:\r | |
529 | self.configure_logger(name, loggers[name], True)\r | |
530 | except StandardError, e:\r | |
531 | raise ValueError('Unable to configure logger '\r | |
532 | '%r: %s' % (name, e))\r | |
533 | root = config.get('root', None)\r | |
534 | if root:\r | |
535 | try:\r | |
536 | self.configure_root(root, True)\r | |
537 | except StandardError, e:\r | |
538 | raise ValueError('Unable to configure root '\r | |
539 | 'logger: %s' % e)\r | |
540 | else:\r | |
541 | disable_existing = config.pop('disable_existing_loggers', True)\r | |
542 | \r | |
543 | logging._handlers.clear()\r | |
544 | del logging._handlerList[:]\r | |
545 | \r | |
546 | # Do formatters first - they don't refer to anything else\r | |
547 | formatters = config.get('formatters', EMPTY_DICT)\r | |
548 | for name in formatters:\r | |
549 | try:\r | |
550 | formatters[name] = self.configure_formatter(\r | |
551 | formatters[name])\r | |
552 | except StandardError, e:\r | |
553 | raise ValueError('Unable to configure '\r | |
554 | 'formatter %r: %s' % (name, e))\r | |
555 | # Next, do filters - they don't refer to anything else, either\r | |
556 | filters = config.get('filters', EMPTY_DICT)\r | |
557 | for name in filters:\r | |
558 | try:\r | |
559 | filters[name] = self.configure_filter(filters[name])\r | |
560 | except StandardError, e:\r | |
561 | raise ValueError('Unable to configure '\r | |
562 | 'filter %r: %s' % (name, e))\r | |
563 | \r | |
564 | # Next, do handlers - they refer to formatters and filters\r | |
565 | # As handlers can refer to other handlers, sort the keys\r | |
566 | # to allow a deterministic order of configuration\r | |
567 | handlers = config.get('handlers', EMPTY_DICT)\r | |
568 | for name in sorted(handlers):\r | |
569 | try:\r | |
570 | handler = self.configure_handler(handlers[name])\r | |
571 | handler.name = name\r | |
572 | handlers[name] = handler\r | |
573 | except StandardError, e:\r | |
574 | raise ValueError('Unable to configure handler '\r | |
575 | '%r: %s' % (name, e))\r | |
576 | # Next, do loggers - they refer to handlers and filters\r | |
577 | \r | |
578 | #we don't want to lose the existing loggers,\r | |
579 | #since other threads may have pointers to them.\r | |
580 | #existing is set to contain all existing loggers,\r | |
581 | #and as we go through the new configuration we\r | |
582 | #remove any which are configured. At the end,\r | |
583 | #what's left in existing is the set of loggers\r | |
584 | #which were in the previous configuration but\r | |
585 | #which are not in the new configuration.\r | |
586 | root = logging.root\r | |
587 | existing = root.manager.loggerDict.keys()\r | |
588 | #The list needs to be sorted so that we can\r | |
589 | #avoid disabling child loggers of explicitly\r | |
590 | #named loggers. With a sorted list it is easier\r | |
591 | #to find the child loggers.\r | |
592 | existing.sort(key=_encoded)\r | |
593 | #We'll keep the list of existing loggers\r | |
594 | #which are children of named loggers here...\r | |
595 | child_loggers = []\r | |
596 | #now set up the new ones...\r | |
597 | loggers = config.get('loggers', EMPTY_DICT)\r | |
598 | for name in loggers:\r | |
599 | if name in existing:\r | |
600 | i = existing.index(name)\r | |
601 | prefixed = name + "."\r | |
602 | pflen = len(prefixed)\r | |
603 | num_existing = len(existing)\r | |
604 | i = i + 1 # look at the entry after name\r | |
605 | while (i < num_existing) and\\r | |
606 | (existing[i][:pflen] == prefixed):\r | |
607 | child_loggers.append(existing[i])\r | |
608 | i = i + 1\r | |
609 | existing.remove(name)\r | |
610 | try:\r | |
611 | self.configure_logger(name, loggers[name])\r | |
612 | except StandardError, e:\r | |
613 | raise ValueError('Unable to configure logger '\r | |
614 | '%r: %s' % (name, e))\r | |
615 | \r | |
616 | #Disable any old loggers. There's no point deleting\r | |
617 | #them as other threads may continue to hold references\r | |
618 | #and by disabling them, you stop them doing any logging.\r | |
619 | #However, don't disable children of named loggers, as that's\r | |
620 | #probably not what was intended by the user.\r | |
621 | for log in existing:\r | |
622 | logger = root.manager.loggerDict[log]\r | |
623 | if log in child_loggers:\r | |
624 | logger.level = logging.NOTSET\r | |
625 | logger.handlers = []\r | |
626 | logger.propagate = True\r | |
627 | elif disable_existing:\r | |
628 | logger.disabled = True\r | |
629 | \r | |
630 | # And finally, do the root logger\r | |
631 | root = config.get('root', None)\r | |
632 | if root:\r | |
633 | try:\r | |
634 | self.configure_root(root)\r | |
635 | except StandardError, e:\r | |
636 | raise ValueError('Unable to configure root '\r | |
637 | 'logger: %s' % e)\r | |
638 | finally:\r | |
639 | logging._releaseLock()\r | |
640 | \r | |
641 | def configure_formatter(self, config):\r | |
642 | """Configure a formatter from a dictionary."""\r | |
643 | if '()' in config:\r | |
644 | factory = config['()'] # for use in exception handler\r | |
645 | try:\r | |
646 | result = self.configure_custom(config)\r | |
647 | except TypeError, te:\r | |
648 | if "'format'" not in str(te):\r | |
649 | raise\r | |
650 | #Name of parameter changed from fmt to format.\r | |
651 | #Retry with old name.\r | |
652 | #This is so that code can be used with older Python versions\r | |
653 | #(e.g. by Django)\r | |
654 | config['fmt'] = config.pop('format')\r | |
655 | config['()'] = factory\r | |
656 | result = self.configure_custom(config)\r | |
657 | else:\r | |
658 | fmt = config.get('format', None)\r | |
659 | dfmt = config.get('datefmt', None)\r | |
660 | result = logging.Formatter(fmt, dfmt)\r | |
661 | return result\r | |
662 | \r | |
663 | def configure_filter(self, config):\r | |
664 | """Configure a filter from a dictionary."""\r | |
665 | if '()' in config:\r | |
666 | result = self.configure_custom(config)\r | |
667 | else:\r | |
668 | name = config.get('name', '')\r | |
669 | result = logging.Filter(name)\r | |
670 | return result\r | |
671 | \r | |
672 | def add_filters(self, filterer, filters):\r | |
673 | """Add filters to a filterer from a list of names."""\r | |
674 | for f in filters:\r | |
675 | try:\r | |
676 | filterer.addFilter(self.config['filters'][f])\r | |
677 | except StandardError, e:\r | |
678 | raise ValueError('Unable to add filter %r: %s' % (f, e))\r | |
679 | \r | |
680 | def configure_handler(self, config):\r | |
681 | """Configure a handler from a dictionary."""\r | |
682 | formatter = config.pop('formatter', None)\r | |
683 | if formatter:\r | |
684 | try:\r | |
685 | formatter = self.config['formatters'][formatter]\r | |
686 | except StandardError, e:\r | |
687 | raise ValueError('Unable to set formatter '\r | |
688 | '%r: %s' % (formatter, e))\r | |
689 | level = config.pop('level', None)\r | |
690 | filters = config.pop('filters', None)\r | |
691 | if '()' in config:\r | |
692 | c = config.pop('()')\r | |
693 | if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:\r | |
694 | c = self.resolve(c)\r | |
695 | factory = c\r | |
696 | else:\r | |
697 | klass = self.resolve(config.pop('class'))\r | |
698 | #Special case for handler which refers to another handler\r | |
699 | if issubclass(klass, logging.handlers.MemoryHandler) and\\r | |
700 | 'target' in config:\r | |
701 | try:\r | |
702 | config['target'] = self.config['handlers'][config['target']]\r | |
703 | except StandardError, e:\r | |
704 | raise ValueError('Unable to set target handler '\r | |
705 | '%r: %s' % (config['target'], e))\r | |
706 | elif issubclass(klass, logging.handlers.SMTPHandler) and\\r | |
707 | 'mailhost' in config:\r | |
708 | config['mailhost'] = self.as_tuple(config['mailhost'])\r | |
709 | elif issubclass(klass, logging.handlers.SysLogHandler) and\\r | |
710 | 'address' in config:\r | |
711 | config['address'] = self.as_tuple(config['address'])\r | |
712 | factory = klass\r | |
713 | kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])\r | |
714 | try:\r | |
715 | result = factory(**kwargs)\r | |
716 | except TypeError, te:\r | |
717 | if "'stream'" not in str(te):\r | |
718 | raise\r | |
719 | #The argument name changed from strm to stream\r | |
720 | #Retry with old name.\r | |
721 | #This is so that code can be used with older Python versions\r | |
722 | #(e.g. by Django)\r | |
723 | kwargs['strm'] = kwargs.pop('stream')\r | |
724 | result = factory(**kwargs)\r | |
725 | if formatter:\r | |
726 | result.setFormatter(formatter)\r | |
727 | if level is not None:\r | |
728 | result.setLevel(logging._checkLevel(level))\r | |
729 | if filters:\r | |
730 | self.add_filters(result, filters)\r | |
731 | return result\r | |
732 | \r | |
733 | def add_handlers(self, logger, handlers):\r | |
734 | """Add handlers to a logger from a list of names."""\r | |
735 | for h in handlers:\r | |
736 | try:\r | |
737 | logger.addHandler(self.config['handlers'][h])\r | |
738 | except StandardError, e:\r | |
739 | raise ValueError('Unable to add handler %r: %s' % (h, e))\r | |
740 | \r | |
741 | def common_logger_config(self, logger, config, incremental=False):\r | |
742 | """\r | |
743 | Perform configuration which is common to root and non-root loggers.\r | |
744 | """\r | |
745 | level = config.get('level', None)\r | |
746 | if level is not None:\r | |
747 | logger.setLevel(logging._checkLevel(level))\r | |
748 | if not incremental:\r | |
749 | #Remove any existing handlers\r | |
750 | for h in logger.handlers[:]:\r | |
751 | logger.removeHandler(h)\r | |
752 | handlers = config.get('handlers', None)\r | |
753 | if handlers:\r | |
754 | self.add_handlers(logger, handlers)\r | |
755 | filters = config.get('filters', None)\r | |
756 | if filters:\r | |
757 | self.add_filters(logger, filters)\r | |
758 | \r | |
759 | def configure_logger(self, name, config, incremental=False):\r | |
760 | """Configure a non-root logger from a dictionary."""\r | |
761 | logger = logging.getLogger(name)\r | |
762 | self.common_logger_config(logger, config, incremental)\r | |
763 | propagate = config.get('propagate', None)\r | |
764 | if propagate is not None:\r | |
765 | logger.propagate = propagate\r | |
766 | \r | |
767 | def configure_root(self, config, incremental=False):\r | |
768 | """Configure a root logger from a dictionary."""\r | |
769 | root = logging.getLogger()\r | |
770 | self.common_logger_config(root, config, incremental)\r | |
771 | \r | |
772 | dictConfigClass = DictConfigurator\r | |
773 | \r | |
774 | def dictConfig(config):\r | |
775 | """Configure logging using a dictionary."""\r | |
776 | dictConfigClass(config).configure()\r | |
777 | \r | |
778 | \r | |
779 | def listen(port=DEFAULT_LOGGING_CONFIG_PORT):\r | |
780 | """\r | |
781 | Start up a socket server on the specified port, and listen for new\r | |
782 | configurations.\r | |
783 | \r | |
784 | These will be sent as a file suitable for processing by fileConfig().\r | |
785 | Returns a Thread object on which you can call start() to start the server,\r | |
786 | and which you can join() when appropriate. To stop the server, call\r | |
787 | stopListening().\r | |
788 | """\r | |
789 | if not thread:\r | |
790 | raise NotImplementedError("listen() needs threading to work")\r | |
791 | \r | |
792 | class ConfigStreamHandler(StreamRequestHandler):\r | |
793 | """\r | |
794 | Handler for a logging configuration request.\r | |
795 | \r | |
796 | It expects a completely new logging configuration and uses fileConfig\r | |
797 | to install it.\r | |
798 | """\r | |
799 | def handle(self):\r | |
800 | """\r | |
801 | Handle a request.\r | |
802 | \r | |
803 | Each request is expected to be a 4-byte length, packed using\r | |
804 | struct.pack(">L", n), followed by the config file.\r | |
805 | Uses fileConfig() to do the grunt work.\r | |
806 | """\r | |
807 | import tempfile\r | |
808 | try:\r | |
809 | conn = self.connection\r | |
810 | chunk = conn.recv(4)\r | |
811 | if len(chunk) == 4:\r | |
812 | slen = struct.unpack(">L", chunk)[0]\r | |
813 | chunk = self.connection.recv(slen)\r | |
814 | while len(chunk) < slen:\r | |
815 | chunk = chunk + conn.recv(slen - len(chunk))\r | |
816 | try:\r | |
817 | import json\r | |
818 | d =json.loads(chunk)\r | |
819 | assert isinstance(d, dict)\r | |
820 | dictConfig(d)\r | |
821 | except:\r | |
822 | #Apply new configuration.\r | |
823 | \r | |
824 | file = cStringIO.StringIO(chunk)\r | |
825 | try:\r | |
826 | fileConfig(file)\r | |
827 | except (KeyboardInterrupt, SystemExit):\r | |
828 | raise\r | |
829 | except:\r | |
830 | traceback.print_exc()\r | |
831 | if self.server.ready:\r | |
832 | self.server.ready.set()\r | |
833 | except socket.error, e:\r | |
834 | if not isinstance(e.args, tuple):\r | |
835 | raise\r | |
836 | else:\r | |
837 | errcode = e.args[0]\r | |
838 | if errcode != RESET_ERROR:\r | |
839 | raise\r | |
840 | \r | |
841 | class ConfigSocketReceiver(ThreadingTCPServer):\r | |
842 | """\r | |
843 | A simple TCP socket-based logging config receiver.\r | |
844 | """\r | |
845 | \r | |
846 | allow_reuse_address = 1\r | |
847 | \r | |
848 | def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,\r | |
849 | handler=None, ready=None):\r | |
850 | ThreadingTCPServer.__init__(self, (host, port), handler)\r | |
851 | logging._acquireLock()\r | |
852 | self.abort = 0\r | |
853 | logging._releaseLock()\r | |
854 | self.timeout = 1\r | |
855 | self.ready = ready\r | |
856 | \r | |
857 | def serve_until_stopped(self):\r | |
858 | import select\r | |
859 | abort = 0\r | |
860 | while not abort:\r | |
861 | rd, wr, ex = select.select([self.socket.fileno()],\r | |
862 | [], [],\r | |
863 | self.timeout)\r | |
864 | if rd:\r | |
865 | self.handle_request()\r | |
866 | logging._acquireLock()\r | |
867 | abort = self.abort\r | |
868 | logging._releaseLock()\r | |
869 | self.socket.close()\r | |
870 | \r | |
871 | class Server(threading.Thread):\r | |
872 | \r | |
873 | def __init__(self, rcvr, hdlr, port):\r | |
874 | super(Server, self).__init__()\r | |
875 | self.rcvr = rcvr\r | |
876 | self.hdlr = hdlr\r | |
877 | self.port = port\r | |
878 | self.ready = threading.Event()\r | |
879 | \r | |
880 | def run(self):\r | |
881 | server = self.rcvr(port=self.port, handler=self.hdlr,\r | |
882 | ready=self.ready)\r | |
883 | if self.port == 0:\r | |
884 | self.port = server.server_address[1]\r | |
885 | self.ready.set()\r | |
886 | global _listener\r | |
887 | logging._acquireLock()\r | |
888 | _listener = server\r | |
889 | logging._releaseLock()\r | |
890 | server.serve_until_stopped()\r | |
891 | \r | |
892 | return Server(ConfigSocketReceiver, ConfigStreamHandler, port)\r | |
893 | \r | |
894 | def stopListening():\r | |
895 | """\r | |
896 | Stop the listening server which was created with a call to listen().\r | |
897 | """\r | |
898 | global _listener\r | |
899 | logging._acquireLock()\r | |
900 | try:\r | |
901 | if _listener:\r | |
902 | _listener.abort = 1\r | |
903 | _listener = None\r | |
904 | finally:\r | |
905 | logging._releaseLock()\r |