~inkscape.dev/inkscape-devlibs/trunk

« back to all changes in this revision

Viewing changes to python/Lib/logging/config.py

  • Committer: Eduard Braun
  • Date: 2016-10-22 16:54:41 UTC
  • Revision ID: eduard.braun2@gmx.de-20161022165441-gfp6agtut9nh4p22
Update Python to version 2.7.12

Included modules:
  coverage 4.2
  lxml 3.6.4
  numpy 1.11.2
  scour 0.35
  six 1.10.0

Show diffs side-by-side

added added

removed removed

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