Fixed bug in fileConfig() which failed to clear logging._handlerList
[python.git] / Lib / logging / config.py
blob511e65937e525d09f9ad00fc6157394e48952149
1 # Copyright 2001-2005 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 Should work under Python versions >= 1.5.2, except that source line
23 information is not available unless 'sys._getframe()' is.
25 Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved.
27 To use, simply 'import logging' and log away!
28 """
30 import sys, logging, logging.handlers, string, socket, struct, os, traceback
32 try:
33 import thread
34 import threading
35 except ImportError:
36 thread = None
38 from SocketServer import ThreadingTCPServer, StreamRequestHandler
41 DEFAULT_LOGGING_CONFIG_PORT = 9030
43 if sys.platform == "win32":
44 RESET_ERROR = 10054 #WSAECONNRESET
45 else:
46 RESET_ERROR = 104 #ECONNRESET
49 # The following code implements a socket listener for on-the-fly
50 # reconfiguration of logging.
52 # _listener holds the server object doing the listening
53 _listener = None
55 def fileConfig(fname, defaults=None):
56 """
57 Read the logging configuration from a ConfigParser-format file.
59 This can be called several times from an application, allowing an end user
60 the ability to select from various pre-canned configurations (if the
61 developer provides a mechanism to present the choices and load the chosen
62 configuration).
63 In versions of ConfigParser which have the readfp method [typically
64 shipped in 2.x versions of Python], you can pass in a file-like object
65 rather than a filename, in which case the file-like object will be read
66 using readfp.
67 """
68 import ConfigParser
70 cp = ConfigParser.ConfigParser(defaults)
71 if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
72 cp.readfp(fname)
73 else:
74 cp.read(fname)
76 formatters = _create_formatters(cp)
78 # critical section
79 logging._acquireLock()
80 try:
81 logging._handlers.clear()
82 logging._handlerList = []
83 # Handlers add themselves to logging._handlers
84 handlers = _install_handlers(cp, formatters)
85 _install_loggers(cp, handlers)
86 finally:
87 logging._releaseLock()
90 def _resolve(name):
91 """Resolve a dotted name to a global object."""
92 name = string.split(name, '.')
93 used = name.pop(0)
94 found = __import__(used)
95 for n in name:
96 used = used + '.' + n
97 try:
98 found = getattr(found, n)
99 except AttributeError:
100 __import__(used)
101 found = getattr(found, n)
102 return found
105 def _create_formatters(cp):
106 """Create and return formatters"""
107 flist = cp.get("formatters", "keys")
108 if not len(flist):
109 return {}
110 flist = string.split(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 = string.split(hlist, ",")
139 handlers = {}
140 fixups = [] #for inter-handler references
141 for hand in hlist:
142 sectname = "handler_%s" % hand
143 klass = cp.get(sectname, "class")
144 opts = cp.options(sectname)
145 if "formatter" in opts:
146 fmt = cp.get(sectname, "formatter")
147 else:
148 fmt = ""
149 klass = eval(klass, vars(logging))
150 args = cp.get(sectname, "args")
151 args = eval(args, vars(logging))
152 h = apply(klass, args)
153 if "level" in opts:
154 level = cp.get(sectname, "level")
155 h.setLevel(logging._levelNames[level])
156 if len(fmt):
157 h.setFormatter(formatters[fmt])
158 #temporary hack for FileHandler and MemoryHandler.
159 if klass == logging.handlers.MemoryHandler:
160 if "target" in opts:
161 target = cp.get(sectname,"target")
162 else:
163 target = ""
164 if len(target): #the target handler may not be loaded yet, so keep for later...
165 fixups.append((h, target))
166 handlers[hand] = h
167 #now all handlers are loaded, fixup inter-handler references...
168 for h, t in fixups:
169 h.setTarget(handlers[t])
170 return handlers
173 def _install_loggers(cp, handlers):
174 """Create and install loggers"""
176 # configure the root first
177 llist = cp.get("loggers", "keys")
178 llist = string.split(llist, ",")
179 llist.remove("root")
180 sectname = "logger_root"
181 root = logging.root
182 log = root
183 opts = cp.options(sectname)
184 if "level" in opts:
185 level = cp.get(sectname, "level")
186 log.setLevel(logging._levelNames[level])
187 for h in root.handlers[:]:
188 root.removeHandler(h)
189 hlist = cp.get(sectname, "handlers")
190 if len(hlist):
191 hlist = string.split(hlist, ",")
192 for hand in hlist:
193 log.addHandler(handlers[hand])
195 #and now the others...
196 #we don't want to lose the existing loggers,
197 #since other threads may have pointers to them.
198 #existing is set to contain all existing loggers,
199 #and as we go through the new configuration we
200 #remove any which are configured. At the end,
201 #what's left in existing is the set of loggers
202 #which were in the previous configuration but
203 #which are not in the new configuration.
204 existing = root.manager.loggerDict.keys()
205 #now set up the new ones...
206 for log in llist:
207 sectname = "logger_%s" % log
208 qn = cp.get(sectname, "qualname")
209 opts = cp.options(sectname)
210 if "propagate" in opts:
211 propagate = cp.getint(sectname, "propagate")
212 else:
213 propagate = 1
214 logger = logging.getLogger(qn)
215 if qn in existing:
216 existing.remove(qn)
217 if "level" in opts:
218 level = cp.get(sectname, "level")
219 logger.setLevel(logging._levelNames[level])
220 for h in logger.handlers[:]:
221 logger.removeHandler(h)
222 logger.propagate = propagate
223 logger.disabled = 0
224 hlist = cp.get(sectname, "handlers")
225 if len(hlist):
226 hlist = string.split(hlist, ",")
227 for hand in hlist:
228 logger.addHandler(handlers[hand])
230 #Disable any old loggers. There's no point deleting
231 #them as other threads may continue to hold references
232 #and by disabling them, you stop them doing any logging.
233 for log in existing:
234 root.manager.loggerDict[log].disabled = 1
237 def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
239 Start up a socket server on the specified port, and listen for new
240 configurations.
242 These will be sent as a file suitable for processing by fileConfig().
243 Returns a Thread object on which you can call start() to start the server,
244 and which you can join() when appropriate. To stop the server, call
245 stopListening().
247 if not thread:
248 raise NotImplementedError, "listen() needs threading to work"
250 class ConfigStreamHandler(StreamRequestHandler):
252 Handler for a logging configuration request.
254 It expects a completely new logging configuration and uses fileConfig
255 to install it.
257 def handle(self):
259 Handle a request.
261 Each request is expected to be a 4-byte length, packed using
262 struct.pack(">L", n), followed by the config file.
263 Uses fileConfig() to do the grunt work.
265 import tempfile
266 try:
267 conn = self.connection
268 chunk = conn.recv(4)
269 if len(chunk) == 4:
270 slen = struct.unpack(">L", chunk)[0]
271 chunk = self.connection.recv(slen)
272 while len(chunk) < slen:
273 chunk = chunk + conn.recv(slen - len(chunk))
274 #Apply new configuration. We'd like to be able to
275 #create a StringIO and pass that in, but unfortunately
276 #1.5.2 ConfigParser does not support reading file
277 #objects, only actual files. So we create a temporary
278 #file and remove it later.
279 file = tempfile.mktemp(".ini")
280 f = open(file, "w")
281 f.write(chunk)
282 f.close()
283 try:
284 fileConfig(file)
285 except (KeyboardInterrupt, SystemExit):
286 raise
287 except:
288 traceback.print_exc()
289 os.remove(file)
290 except socket.error, e:
291 if type(e.args) != types.TupleType:
292 raise
293 else:
294 errcode = e.args[0]
295 if errcode != RESET_ERROR:
296 raise
298 class ConfigSocketReceiver(ThreadingTCPServer):
300 A simple TCP socket-based logging config receiver.
303 allow_reuse_address = 1
305 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
306 handler=None):
307 ThreadingTCPServer.__init__(self, (host, port), handler)
308 logging._acquireLock()
309 self.abort = 0
310 logging._releaseLock()
311 self.timeout = 1
313 def serve_until_stopped(self):
314 import select
315 abort = 0
316 while not abort:
317 rd, wr, ex = select.select([self.socket.fileno()],
318 [], [],
319 self.timeout)
320 if rd:
321 self.handle_request()
322 logging._acquireLock()
323 abort = self.abort
324 logging._releaseLock()
326 def serve(rcvr, hdlr, port):
327 server = rcvr(port=port, handler=hdlr)
328 global _listener
329 logging._acquireLock()
330 _listener = server
331 logging._releaseLock()
332 server.serve_until_stopped()
334 return threading.Thread(target=serve,
335 args=(ConfigSocketReceiver,
336 ConfigStreamHandler, port))
338 def stopListening():
340 Stop the listening server which was created with a call to listen().
342 global _listener
343 if _listener:
344 logging._acquireLock()
345 _listener.abort = 1
346 _listener = None
347 logging._releaseLock()