Fixed issue-number mistake in NEWS update.
[python.git] / Lib / logging / config.py
blob99403d27c383f1341a72e4abf876f674ffe45059
1 # Copyright 2001-2007 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.
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.
22 Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved.
24 To use, simply 'import logging' and log away!
25 """
27 import sys, logging, logging.handlers, socket, struct, os, traceback
29 try:
30 import thread
31 import threading
32 except ImportError:
33 thread = None
35 from SocketServer import ThreadingTCPServer, StreamRequestHandler
38 DEFAULT_LOGGING_CONFIG_PORT = 9030
40 if sys.platform == "win32":
41 RESET_ERROR = 10054 #WSAECONNRESET
42 else:
43 RESET_ERROR = 104 #ECONNRESET
46 # The following code implements a socket listener for on-the-fly
47 # reconfiguration of logging.
49 # _listener holds the server object doing the listening
50 _listener = None
52 def fileConfig(fname, defaults=None, disable_existing_loggers=True):
53 """
54 Read the logging configuration from a ConfigParser-format file.
56 This can be called several times from an application, allowing an end user
57 the ability to select from various pre-canned configurations (if the
58 developer provides a mechanism to present the choices and load the chosen
59 configuration).
60 In versions of ConfigParser which have the readfp method [typically
61 shipped in 2.x versions of Python], you can pass in a file-like object
62 rather than a filename, in which case the file-like object will be read
63 using readfp.
64 """
65 import ConfigParser
67 cp = ConfigParser.ConfigParser(defaults)
68 if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
69 cp.readfp(fname)
70 else:
71 cp.read(fname)
73 formatters = _create_formatters(cp)
75 # critical section
76 logging._acquireLock()
77 try:
78 logging._handlers.clear()
79 del logging._handlerList[:]
80 # Handlers add themselves to logging._handlers
81 handlers = _install_handlers(cp, formatters)
82 _install_loggers(cp, handlers, disable_existing_loggers)
83 finally:
84 logging._releaseLock()
87 def _resolve(name):
88 """Resolve a dotted name to a global object."""
89 name = name.split('.')
90 used = name.pop(0)
91 found = __import__(used)
92 for n in name:
93 used = used + '.' + n
94 try:
95 found = getattr(found, n)
96 except AttributeError:
97 __import__(used)
98 found = getattr(found, n)
99 return found
101 def _strip_spaces(alist):
102 return map(lambda x: x.strip(), alist)
104 def _create_formatters(cp):
105 """Create and return formatters"""
106 flist = cp.get("formatters", "keys")
107 if not len(flist):
108 return {}
109 flist = flist.split(",")
110 flist = _strip_spaces(flist)
111 formatters = {}
112 for form in flist:
113 sectname = "formatter_%s" % form
114 opts = cp.options(sectname)
115 if "format" in opts:
116 fs = cp.get(sectname, "format", 1)
117 else:
118 fs = None
119 if "datefmt" in opts:
120 dfs = cp.get(sectname, "datefmt", 1)
121 else:
122 dfs = None
123 c = logging.Formatter
124 if "class" in opts:
125 class_name = cp.get(sectname, "class")
126 if class_name:
127 c = _resolve(class_name)
128 f = c(fs, dfs)
129 formatters[form] = f
130 return formatters
133 def _install_handlers(cp, formatters):
134 """Install and return handlers"""
135 hlist = cp.get("handlers", "keys")
136 if not len(hlist):
137 return {}
138 hlist = hlist.split(",")
139 hlist = _strip_spaces(hlist)
140 handlers = {}
141 fixups = [] #for inter-handler references
142 for hand in hlist:
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")
148 else:
149 fmt = ""
150 try:
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))
156 h = klass(*args)
157 if "level" in opts:
158 level = cp.get(sectname, "level")
159 h.setLevel(logging._levelNames[level])
160 if len(fmt):
161 h.setFormatter(formatters[fmt])
162 if issubclass(klass, logging.handlers.MemoryHandler):
163 if "target" in opts:
164 target = cp.get(sectname,"target")
165 else:
166 target = ""
167 if len(target): #the target handler may not be loaded yet, so keep for later...
168 fixups.append((h, target))
169 handlers[hand] = h
170 #now all handlers are loaded, fixup inter-handler references...
171 for h, t in fixups:
172 h.setTarget(handlers[t])
173 return handlers
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))
183 llist.remove("root")
184 sectname = "logger_root"
185 root = logging.root
186 log = root
187 opts = cp.options(sectname)
188 if "level" in opts:
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")
194 if len(hlist):
195 hlist = hlist.split(",")
196 hlist = _strip_spaces(hlist)
197 for hand in 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()
215 #We'll keep the list of existing loggers
216 #which are children of named loggers here...
217 child_loggers = []
218 #now set up the new ones...
219 for log in llist:
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")
225 else:
226 propagate = 1
227 logger = logging.getLogger(qn)
228 if qn in existing:
229 i = existing.index(qn)
230 prefixed = qn + "."
231 pflen = len(prefixed)
232 num_existing = len(existing)
233 i = i + 1 # look at the entry after qn
234 while (i < num_existing) and (existing[i][:pflen] == prefixed):
235 child_loggers.append(existing[i])
236 i = i + 1
237 existing.remove(qn)
238 if "level" in opts:
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
244 logger.disabled = 0
245 hlist = cp.get(sectname, "handlers")
246 if len(hlist):
247 hlist = hlist.split(",")
248 hlist = _strip_spaces(hlist)
249 for hand in 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.
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
267 def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
269 Start up a socket server on the specified port, and listen for new
270 configurations.
272 These will be sent as a file suitable for processing by fileConfig().
273 Returns a Thread object on which you can call start() to start the server,
274 and which you can join() when appropriate. To stop the server, call
275 stopListening().
277 if not thread:
278 raise NotImplementedError("listen() needs threading to work")
280 class ConfigStreamHandler(StreamRequestHandler):
282 Handler for a logging configuration request.
284 It expects a completely new logging configuration and uses fileConfig
285 to install it.
287 def handle(self):
289 Handle a request.
291 Each request is expected to be a 4-byte length, packed using
292 struct.pack(">L", n), followed by the config file.
293 Uses fileConfig() to do the grunt work.
295 import tempfile
296 try:
297 conn = self.connection
298 chunk = conn.recv(4)
299 if len(chunk) == 4:
300 slen = struct.unpack(">L", chunk)[0]
301 chunk = self.connection.recv(slen)
302 while len(chunk) < slen:
303 chunk = chunk + conn.recv(slen - len(chunk))
304 #Apply new configuration. We'd like to be able to
305 #create a StringIO and pass that in, but unfortunately
306 #1.5.2 ConfigParser does not support reading file
307 #objects, only actual files. So we create a temporary
308 #file and remove it later.
309 file = tempfile.mktemp(".ini")
310 f = open(file, "w")
311 f.write(chunk)
312 f.close()
313 try:
314 fileConfig(file)
315 except (KeyboardInterrupt, SystemExit):
316 raise
317 except:
318 traceback.print_exc()
319 os.remove(file)
320 except socket.error, e:
321 if not isinstance(e.args, tuple):
322 raise
323 else:
324 errcode = e.args[0]
325 if errcode != RESET_ERROR:
326 raise
328 class ConfigSocketReceiver(ThreadingTCPServer):
330 A simple TCP socket-based logging config receiver.
333 allow_reuse_address = 1
335 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
336 handler=None):
337 ThreadingTCPServer.__init__(self, (host, port), handler)
338 logging._acquireLock()
339 self.abort = 0
340 logging._releaseLock()
341 self.timeout = 1
343 def serve_until_stopped(self):
344 import select
345 abort = 0
346 while not abort:
347 rd, wr, ex = select.select([self.socket.fileno()],
348 [], [],
349 self.timeout)
350 if rd:
351 self.handle_request()
352 logging._acquireLock()
353 abort = self.abort
354 logging._releaseLock()
356 def serve(rcvr, hdlr, port):
357 server = rcvr(port=port, handler=hdlr)
358 global _listener
359 logging._acquireLock()
360 _listener = server
361 logging._releaseLock()
362 server.serve_until_stopped()
364 return threading.Thread(target=serve,
365 args=(ConfigSocketReceiver,
366 ConfigStreamHandler, port))
368 def stopListening():
370 Stop the listening server which was created with a call to listen().
372 global _listener
373 if _listener:
374 logging._acquireLock()
375 _listener.abort = 1
376 _listener = None
377 logging._releaseLock()