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