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