1 # Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
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.
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.
22 Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
24 To use, simply 'import logging' and log away!
27 import sys
, logging
, logging
.handlers
, socket
, struct
, os
, traceback
, re
28 import types
, cStringIO
36 from SocketServer
import ThreadingTCPServer
, StreamRequestHandler
39 DEFAULT_LOGGING_CONFIG_PORT
= 9030
41 if sys
.platform
== "win32":
42 RESET_ERROR
= 10054 #WSAECONNRESET
44 RESET_ERROR
= 104 #ECONNRESET
47 # The following code implements a socket listener for on-the-fly
48 # reconfiguration of logging.
50 # _listener holds the server object doing the listening
53 def fileConfig(fname
, defaults
=None, disable_existing_loggers
=True):
55 Read the logging configuration from a ConfigParser-format file.
57 This can be called several times from an application, allowing an end user
58 the ability to select from various pre-canned configurations (if the
59 developer provides a mechanism to present the choices and load the chosen
64 cp
= ConfigParser
.ConfigParser(defaults
)
65 if hasattr(fname
, 'readline'):
70 formatters
= _create_formatters(cp
)
73 logging
._acquireLock
()
75 logging
._handlers
.clear()
76 del logging
._handlerList
[:]
77 # Handlers add themselves to logging._handlers
78 handlers
= _install_handlers(cp
, formatters
)
79 _install_loggers(cp
, handlers
, disable_existing_loggers
)
81 logging
._releaseLock
()
85 """Resolve a dotted name to a global object."""
86 name
= name
.split('.')
88 found
= __import__(used
)
92 found
= getattr(found
, n
)
93 except AttributeError:
95 found
= getattr(found
, n
)
98 def _strip_spaces(alist
):
99 return map(lambda x
: x
.strip(), alist
)
102 return s
if isinstance(s
, str) else s
.encode('utf-8')
104 def _create_formatters(cp
):
105 """Create and return formatters"""
106 flist
= cp
.get("formatters", "keys")
109 flist
= flist
.split(",")
110 flist
= _strip_spaces(flist
)
113 sectname
= "formatter_%s" % form
114 opts
= cp
.options(sectname
)
116 fs
= cp
.get(sectname
, "format", 1)
119 if "datefmt" in opts
:
120 dfs
= cp
.get(sectname
, "datefmt", 1)
123 c
= logging
.Formatter
125 class_name
= cp
.get(sectname
, "class")
127 c
= _resolve(class_name
)
133 def _install_handlers(cp
, formatters
):
134 """Install and return handlers"""
135 hlist
= cp
.get("handlers", "keys")
138 hlist
= hlist
.split(",")
139 hlist
= _strip_spaces(hlist
)
141 fixups
= [] #for inter-handler references
143 sectname
= "handler_%s" % hand
144 klass
= cp
.get(sectname
, "class")
145 opts
= cp
.options(sectname
)
146 if "formatter" in opts
:
147 fmt
= cp
.get(sectname
, "formatter")
151 klass
= eval(klass
, vars(logging
))
152 except (AttributeError, NameError):
153 klass
= _resolve(klass
)
154 args
= cp
.get(sectname
, "args")
155 args
= eval(args
, vars(logging
))
158 level
= cp
.get(sectname
, "level")
159 h
.setLevel(logging
._levelNames
[level
])
161 h
.setFormatter(formatters
[fmt
])
162 if issubclass(klass
, logging
.handlers
.MemoryHandler
):
164 target
= cp
.get(sectname
,"target")
167 if len(target
): #the target handler may not be loaded yet, so keep for later...
168 fixups
.append((h
, target
))
170 #now all handlers are loaded, fixup inter-handler references...
172 h
.setTarget(handlers
[t
])
176 def _install_loggers(cp
, handlers
, disable_existing_loggers
):
177 """Create and install loggers"""
179 # configure the root first
180 llist
= cp
.get("loggers", "keys")
181 llist
= llist
.split(",")
182 llist
= list(map(lambda x
: x
.strip(), llist
))
184 sectname
= "logger_root"
187 opts
= cp
.options(sectname
)
189 level
= cp
.get(sectname
, "level")
190 log
.setLevel(logging
._levelNames
[level
])
191 for h
in root
.handlers
[:]:
192 root
.removeHandler(h
)
193 hlist
= cp
.get(sectname
, "handlers")
195 hlist
= hlist
.split(",")
196 hlist
= _strip_spaces(hlist
)
198 log
.addHandler(handlers
[hand
])
200 #and now the others...
201 #we don't want to lose the existing loggers,
202 #since other threads may have pointers to them.
203 #existing is set to contain all existing loggers,
204 #and as we go through the new configuration we
205 #remove any which are configured. At the end,
206 #what's left in existing is the set of loggers
207 #which were in the previous configuration but
208 #which are not in the new configuration.
209 existing
= list(root
.manager
.loggerDict
.keys())
210 #The list needs to be sorted so that we can
211 #avoid disabling child loggers of explicitly
212 #named loggers. With a sorted list it is easier
213 #to find the child loggers.
214 existing
.sort(key
=_encoded
)
215 #We'll keep the list of existing loggers
216 #which are children of named loggers here...
218 #now set up the new ones...
220 sectname
= "logger_%s" % log
221 qn
= cp
.get(sectname
, "qualname")
222 opts
= cp
.options(sectname
)
223 if "propagate" in opts
:
224 propagate
= cp
.getint(sectname
, "propagate")
227 logger
= logging
.getLogger(qn
)
229 i
= existing
.index(qn
) + 1 # start with the entry after qn
231 pflen
= len(prefixed
)
232 num_existing
= len(existing
)
233 while i
< num_existing
:
234 if existing
[i
][:pflen
] == prefixed
:
235 child_loggers
.append(existing
[i
])
239 level
= cp
.get(sectname
, "level")
240 logger
.setLevel(logging
._levelNames
[level
])
241 for h
in logger
.handlers
[:]:
242 logger
.removeHandler(h
)
243 logger
.propagate
= propagate
245 hlist
= cp
.get(sectname
, "handlers")
247 hlist
= hlist
.split(",")
248 hlist
= _strip_spaces(hlist
)
250 logger
.addHandler(handlers
[hand
])
252 #Disable any old loggers. There's no point deleting
253 #them as other threads may continue to hold references
254 #and by disabling them, you stop them doing any logging.
255 #However, don't disable children of named loggers, as that's
256 #probably not what was intended by the user.
258 logger
= root
.manager
.loggerDict
[log
]
259 if log
in child_loggers
:
260 logger
.level
= logging
.NOTSET
263 elif disable_existing_loggers
:
268 IDENTIFIER
= re
.compile('^[a-z_][a-z0-9_]*$', re
.I
)
272 m
= IDENTIFIER
.match(s
)
274 raise ValueError('Not a valid Python identifier: %r' % s
)
278 # The ConvertingXXX classes are wrappers around standard Python containers,
279 # and they serve to convert any suitable values in the container. The
280 # conversion converts base dicts, lists and tuples to their wrapped
281 # equivalents, whereas strings which match a conversion format are converted
284 # Each wrapper should have a configurator attribute holding the actual
285 # configurator to use for conversion.
287 class ConvertingDict(dict):
288 """A converting dictionary wrapper."""
290 def __getitem__(self
, key
):
291 value
= dict.__getitem
__(self
, key
)
292 result
= self
.configurator
.convert(value
)
293 #If the converted value is different, save for next time
294 if value
is not result
:
296 if type(result
) in (ConvertingDict
, ConvertingList
,
302 def get(self
, key
, default
=None):
303 value
= dict.get(self
, key
, default
)
304 result
= self
.configurator
.convert(value
)
305 #If the converted value is different, save for next time
306 if value
is not result
:
308 if type(result
) in (ConvertingDict
, ConvertingList
,
314 def pop(self
, key
, default
=None):
315 value
= dict.pop(self
, key
, default
)
316 result
= self
.configurator
.convert(value
)
317 if value
is not result
:
318 if type(result
) in (ConvertingDict
, ConvertingList
,
324 class ConvertingList(list):
325 """A converting list wrapper."""
326 def __getitem__(self
, key
):
327 value
= list.__getitem
__(self
, key
)
328 result
= self
.configurator
.convert(value
)
329 #If the converted value is different, save for next time
330 if value
is not result
:
332 if type(result
) in (ConvertingDict
, ConvertingList
,
338 def pop(self
, idx
=-1):
339 value
= list.pop(self
, idx
)
340 result
= self
.configurator
.convert(value
)
341 if value
is not result
:
342 if type(result
) in (ConvertingDict
, ConvertingList
,
347 class ConvertingTuple(tuple):
348 """A converting tuple wrapper."""
349 def __getitem__(self
, key
):
350 value
= tuple.__getitem
__(self
, key
)
351 result
= self
.configurator
.convert(value
)
352 if value
is not result
:
353 if type(result
) in (ConvertingDict
, ConvertingList
,
359 class BaseConfigurator(object):
361 The configurator base class which defines some useful defaults.
364 CONVERT_PATTERN
= re
.compile(r
'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
366 WORD_PATTERN
= re
.compile(r
'^\s*(\w+)\s*')
367 DOT_PATTERN
= re
.compile(r
'^\.\s*(\w+)\s*')
368 INDEX_PATTERN
= re
.compile(r
'^\[\s*(\w+)\s*\]\s*')
369 DIGIT_PATTERN
= re
.compile(r
'^\d+$')
372 'ext' : 'ext_convert',
373 'cfg' : 'cfg_convert',
376 # We might want to use a different one, e.g. importlib
377 importer
= __import__
379 def __init__(self
, config
):
380 self
.config
= ConvertingDict(config
)
381 self
.config
.configurator
= self
383 def resolve(self
, s
):
385 Resolve strings to objects using standard import and attribute
391 found
= self
.importer(used
)
395 found
= getattr(found
, frag
)
396 except AttributeError:
398 found
= getattr(found
, frag
)
401 e
, tb
= sys
.exc_info()[1:]
402 v
= ValueError('Cannot resolve %r: %s' % (s
, e
))
403 v
.__cause
__, v
.__traceback
__ = e
, tb
406 def ext_convert(self
, value
):
407 """Default converter for the ext:// protocol."""
408 return self
.resolve(value
)
410 def cfg_convert(self
, value
):
411 """Default converter for the cfg:// protocol."""
413 m
= self
.WORD_PATTERN
.match(rest
)
415 raise ValueError("Unable to convert %r" % value
)
417 rest
= rest
[m
.end():]
418 d
= self
.config
[m
.groups()[0]]
421 m
= self
.DOT_PATTERN
.match(rest
)
425 m
= self
.INDEX_PATTERN
.match(rest
)
428 if not self
.DIGIT_PATTERN
.match(idx
):
432 n
= int(idx
) # try as number first (most likely)
437 rest
= rest
[m
.end():]
439 raise ValueError('Unable to convert '
440 '%r at %r' % (value
, rest
))
441 #rest should be empty
444 def convert(self
, value
):
446 Convert values to an appropriate type. dicts, lists and tuples are
447 replaced by their converting alternatives. Strings are checked to
448 see if they have a conversion format and are converted if they do.
450 if not isinstance(value
, ConvertingDict
) and isinstance(value
, dict):
451 value
= ConvertingDict(value
)
452 value
.configurator
= self
453 elif not isinstance(value
, ConvertingList
) and isinstance(value
, list):
454 value
= ConvertingList(value
)
455 value
.configurator
= self
456 elif not isinstance(value
, ConvertingTuple
) and\
457 isinstance(value
, tuple):
458 value
= ConvertingTuple(value
)
459 value
.configurator
= self
460 elif isinstance(value
, basestring
): # str for py3k
461 m
= self
.CONVERT_PATTERN
.match(value
)
465 converter
= self
.value_converters
.get(prefix
, None)
468 converter
= getattr(self
, converter
)
469 value
= converter(suffix
)
472 def configure_custom(self
, config
):
473 """Configure an object with a user-supplied factory."""
475 if not hasattr(c
, '__call__') and hasattr(types
, 'ClassType') and type(c
) != types
.ClassType
:
477 props
= config
.pop('.', None)
478 # Check for valid identifiers
479 kwargs
= dict([(k
, config
[k
]) for k
in config
if valid_ident(k
)])
482 for name
, value
in props
.items():
483 setattr(result
, name
, value
)
486 def as_tuple(self
, value
):
487 """Utility function which converts lists to tuples."""
488 if isinstance(value
, list):
492 class DictConfigurator(BaseConfigurator
):
494 Configure logging using a dictionary-like object to describe the
499 """Do the configuration."""
502 if 'version' not in config
:
503 raise ValueError("dictionary doesn't specify a version")
504 if config
['version'] != 1:
505 raise ValueError("Unsupported version: %s" % config
['version'])
506 incremental
= config
.pop('incremental', False)
508 logging
._acquireLock
()
511 handlers
= config
.get('handlers', EMPTY_DICT
)
512 for name
in handlers
:
513 if name
not in logging
._handlers
:
514 raise ValueError('No handler found with '
518 handler
= logging
._handlers
[name
]
519 handler_config
= handlers
[name
]
520 level
= handler_config
.get('level', None)
522 handler
.setLevel(logging
._checkLevel
(level
))
523 except StandardError, e
:
524 raise ValueError('Unable to configure handler '
525 '%r: %s' % (name
, e
))
526 loggers
= config
.get('loggers', EMPTY_DICT
)
529 self
.configure_logger(name
, loggers
[name
], True)
530 except StandardError, e
:
531 raise ValueError('Unable to configure logger '
532 '%r: %s' % (name
, e
))
533 root
= config
.get('root', None)
536 self
.configure_root(root
, True)
537 except StandardError, e
:
538 raise ValueError('Unable to configure root '
541 disable_existing
= config
.pop('disable_existing_loggers', True)
543 logging
._handlers
.clear()
544 del logging
._handlerList
[:]
546 # Do formatters first - they don't refer to anything else
547 formatters
= config
.get('formatters', EMPTY_DICT
)
548 for name
in formatters
:
550 formatters
[name
] = self
.configure_formatter(
552 except StandardError, e
:
553 raise ValueError('Unable to configure '
554 'formatter %r: %s' % (name
, e
))
555 # Next, do filters - they don't refer to anything else, either
556 filters
= config
.get('filters', EMPTY_DICT
)
559 filters
[name
] = self
.configure_filter(filters
[name
])
560 except StandardError, e
:
561 raise ValueError('Unable to configure '
562 'filter %r: %s' % (name
, e
))
564 # Next, do handlers - they refer to formatters and filters
565 # As handlers can refer to other handlers, sort the keys
566 # to allow a deterministic order of configuration
567 handlers
= config
.get('handlers', EMPTY_DICT
)
568 for name
in sorted(handlers
):
570 handler
= self
.configure_handler(handlers
[name
])
572 handlers
[name
] = handler
573 except StandardError, e
:
574 raise ValueError('Unable to configure handler '
575 '%r: %s' % (name
, e
))
576 # Next, do loggers - they refer to handlers and filters
578 #we don't want to lose the existing loggers,
579 #since other threads may have pointers to them.
580 #existing is set to contain all existing loggers,
581 #and as we go through the new configuration we
582 #remove any which are configured. At the end,
583 #what's left in existing is the set of loggers
584 #which were in the previous configuration but
585 #which are not in the new configuration.
587 existing
= root
.manager
.loggerDict
.keys()
588 #The list needs to be sorted so that we can
589 #avoid disabling child loggers of explicitly
590 #named loggers. With a sorted list it is easier
591 #to find the child loggers.
592 existing
.sort(key
=_encoded
)
593 #We'll keep the list of existing loggers
594 #which are children of named loggers here...
596 #now set up the new ones...
597 loggers
= config
.get('loggers', EMPTY_DICT
)
600 i
= existing
.index(name
)
601 prefixed
= name
+ "."
602 pflen
= len(prefixed
)
603 num_existing
= len(existing
)
604 i
= i
+ 1 # look at the entry after name
605 while (i
< num_existing
) and\
606 (existing
[i
][:pflen
] == prefixed
):
607 child_loggers
.append(existing
[i
])
609 existing
.remove(name
)
611 self
.configure_logger(name
, loggers
[name
])
612 except StandardError, e
:
613 raise ValueError('Unable to configure logger '
614 '%r: %s' % (name
, e
))
616 #Disable any old loggers. There's no point deleting
617 #them as other threads may continue to hold references
618 #and by disabling them, you stop them doing any logging.
619 #However, don't disable children of named loggers, as that's
620 #probably not what was intended by the user.
622 logger
= root
.manager
.loggerDict
[log
]
623 if log
in child_loggers
:
624 logger
.level
= logging
.NOTSET
626 logger
.propagate
= True
627 elif disable_existing
:
628 logger
.disabled
= True
630 # And finally, do the root logger
631 root
= config
.get('root', None)
634 self
.configure_root(root
)
635 except StandardError, e
:
636 raise ValueError('Unable to configure root '
639 logging
._releaseLock
()
641 def configure_formatter(self
, config
):
642 """Configure a formatter from a dictionary."""
644 factory
= config
['()'] # for use in exception handler
646 result
= self
.configure_custom(config
)
647 except TypeError, te
:
648 if "'format'" not in str(te
):
650 #Name of parameter changed from fmt to format.
651 #Retry with old name.
652 #This is so that code can be used with older Python versions
654 config
['fmt'] = config
.pop('format')
655 config
['()'] = factory
656 result
= self
.configure_custom(config
)
658 fmt
= config
.get('format', None)
659 dfmt
= config
.get('datefmt', None)
660 result
= logging
.Formatter(fmt
, dfmt
)
663 def configure_filter(self
, config
):
664 """Configure a filter from a dictionary."""
666 result
= self
.configure_custom(config
)
668 name
= config
.get('name', '')
669 result
= logging
.Filter(name
)
672 def add_filters(self
, filterer
, filters
):
673 """Add filters to a filterer from a list of names."""
676 filterer
.addFilter(self
.config
['filters'][f
])
677 except StandardError, e
:
678 raise ValueError('Unable to add filter %r: %s' % (f
, e
))
680 def configure_handler(self
, config
):
681 """Configure a handler from a dictionary."""
682 formatter
= config
.pop('formatter', None)
685 formatter
= self
.config
['formatters'][formatter
]
686 except StandardError, e
:
687 raise ValueError('Unable to set formatter '
688 '%r: %s' % (formatter
, e
))
689 level
= config
.pop('level', None)
690 filters
= config
.pop('filters', None)
693 if not hasattr(c
, '__call__') and hasattr(types
, 'ClassType') and type(c
) != types
.ClassType
:
697 klass
= self
.resolve(config
.pop('class'))
698 #Special case for handler which refers to another handler
699 if issubclass(klass
, logging
.handlers
.MemoryHandler
) and\
702 config
['target'] = self
.config
['handlers'][config
['target']]
703 except StandardError, e
:
704 raise ValueError('Unable to set target handler '
705 '%r: %s' % (config
['target'], e
))
706 elif issubclass(klass
, logging
.handlers
.SMTPHandler
) and\
707 'mailhost' in config
:
708 config
['mailhost'] = self
.as_tuple(config
['mailhost'])
709 elif issubclass(klass
, logging
.handlers
.SysLogHandler
) and\
711 config
['address'] = self
.as_tuple(config
['address'])
713 kwargs
= dict([(k
, config
[k
]) for k
in config
if valid_ident(k
)])
715 result
= factory(**kwargs
)
716 except TypeError, te
:
717 if "'stream'" not in str(te
):
719 #The argument name changed from strm to stream
720 #Retry with old name.
721 #This is so that code can be used with older Python versions
723 kwargs
['strm'] = kwargs
.pop('stream')
724 result
= factory(**kwargs
)
726 result
.setFormatter(formatter
)
727 if level
is not None:
728 result
.setLevel(logging
._checkLevel
(level
))
730 self
.add_filters(result
, filters
)
733 def add_handlers(self
, logger
, handlers
):
734 """Add handlers to a logger from a list of names."""
737 logger
.addHandler(self
.config
['handlers'][h
])
738 except StandardError, e
:
739 raise ValueError('Unable to add handler %r: %s' % (h
, e
))
741 def common_logger_config(self
, logger
, config
, incremental
=False):
743 Perform configuration which is common to root and non-root loggers.
745 level
= config
.get('level', None)
746 if level
is not None:
747 logger
.setLevel(logging
._checkLevel
(level
))
749 #Remove any existing handlers
750 for h
in logger
.handlers
[:]:
751 logger
.removeHandler(h
)
752 handlers
= config
.get('handlers', None)
754 self
.add_handlers(logger
, handlers
)
755 filters
= config
.get('filters', None)
757 self
.add_filters(logger
, filters
)
759 def configure_logger(self
, name
, config
, incremental
=False):
760 """Configure a non-root logger from a dictionary."""
761 logger
= logging
.getLogger(name
)
762 self
.common_logger_config(logger
, config
, incremental
)
763 propagate
= config
.get('propagate', None)
764 if propagate
is not None:
765 logger
.propagate
= propagate
767 def configure_root(self
, config
, incremental
=False):
768 """Configure a root logger from a dictionary."""
769 root
= logging
.getLogger()
770 self
.common_logger_config(root
, config
, incremental
)
772 dictConfigClass
= DictConfigurator
774 def dictConfig(config
):
775 """Configure logging using a dictionary."""
776 dictConfigClass(config
).configure()
779 def listen(port
=DEFAULT_LOGGING_CONFIG_PORT
):
781 Start up a socket server on the specified port, and listen for new
784 These will be sent as a file suitable for processing by fileConfig().
785 Returns a Thread object on which you can call start() to start the server,
786 and which you can join() when appropriate. To stop the server, call
790 raise NotImplementedError("listen() needs threading to work")
792 class ConfigStreamHandler(StreamRequestHandler
):
794 Handler for a logging configuration request.
796 It expects a completely new logging configuration and uses fileConfig
803 Each request is expected to be a 4-byte length, packed using
804 struct.pack(">L", n), followed by the config file.
805 Uses fileConfig() to do the grunt work.
809 conn
= self
.connection
812 slen
= struct
.unpack(">L", chunk
)[0]
813 chunk
= self
.connection
.recv(slen
)
814 while len(chunk
) < slen
:
815 chunk
= chunk
+ conn
.recv(slen
- len(chunk
))
819 assert isinstance(d
, dict)
822 #Apply new configuration.
824 file = cStringIO
.StringIO(chunk
)
827 except (KeyboardInterrupt, SystemExit):
830 traceback
.print_exc()
831 if self
.server
.ready
:
832 self
.server
.ready
.set()
833 except socket
.error
, e
:
834 if not isinstance(e
.args
, tuple):
838 if errcode
!= RESET_ERROR
:
841 class ConfigSocketReceiver(ThreadingTCPServer
):
843 A simple TCP socket-based logging config receiver.
846 allow_reuse_address
= 1
848 def __init__(self
, host
='localhost', port
=DEFAULT_LOGGING_CONFIG_PORT
,
849 handler
=None, ready
=None):
850 ThreadingTCPServer
.__init
__(self
, (host
, port
), handler
)
851 logging
._acquireLock
()
853 logging
._releaseLock
()
857 def serve_until_stopped(self
):
861 rd
, wr
, ex
= select
.select([self
.socket
.fileno()],
865 self
.handle_request()
866 logging
._acquireLock
()
868 logging
._releaseLock
()
871 class Server(threading
.Thread
):
873 def __init__(self
, rcvr
, hdlr
, port
):
874 super(Server
, self
).__init
__()
878 self
.ready
= threading
.Event()
881 server
= self
.rcvr(port
=self
.port
, handler
=self
.hdlr
,
884 self
.port
= server
.server_address
[1]
887 logging
._acquireLock
()
889 logging
._releaseLock
()
890 server
.serve_until_stopped()
892 return Server(ConfigSocketReceiver
, ConfigStreamHandler
, port
)
896 Stop the listening server which was created with a call to listen().
899 logging
._acquireLock
()
905 logging
._releaseLock
()