Added the ability to specify a class attribute in Formatter configuration. Contribute...
[python.git] / Lib / logging / config.py
blob5adfe4dd21d80dccde8b7ff8ac7ae4aef2b4773e
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 # Handlers add themselves to logging._handlers
83 handlers = _install_handlers(cp, formatters)
84 _install_loggers(cp, handlers)
85 finally:
86 logging._releaseLock()
89 def _resolve(name):
90 """Resolve a dotted name to a global object."""
91 name = string.split(name, '.')
92 used = name.pop(0)
93 found = __import__(used)
94 for n in name:
95 used = used + '.' + n
96 try:
97 found = getattr(found, n)
98 except AttributeError:
99 __import__(used)
100 found = getattr(found, n)
101 return found
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 = string.split(flist, ",")
110 formatters = {}
111 for form in flist:
112 sectname = "formatter_%s" % form
113 opts = cp.options(sectname)
114 if "format" in opts:
115 fs = cp.get(sectname, "format", 1)
116 else:
117 fs = None
118 if "datefmt" in opts:
119 dfs = cp.get(sectname, "datefmt", 1)
120 else:
121 dfs = None
122 c = logging.Formatter
123 if "class" in opts:
124 class_name = cp.get(sectname, "class")
125 if class_name:
126 c = _resolve(class_name)
127 f = c(fs, dfs)
128 formatters[form] = f
129 return formatters
132 def _install_handlers(cp, formatters):
133 """Install and return handlers"""
134 hlist = cp.get("handlers", "keys")
135 if not len(hlist):
136 return {}
137 hlist = string.split(hlist, ",")
138 handlers = {}
139 fixups = [] #for inter-handler references
140 for hand in hlist:
141 sectname = "handler_%s" % hand
142 klass = cp.get(sectname, "class")
143 opts = cp.options(sectname)
144 if "formatter" in opts:
145 fmt = cp.get(sectname, "formatter")
146 else:
147 fmt = ""
148 klass = eval(klass, vars(logging))
149 args = cp.get(sectname, "args")
150 args = eval(args, vars(logging))
151 h = apply(klass, args)
152 if "level" in opts:
153 level = cp.get(sectname, "level")
154 h.setLevel(logging._levelNames[level])
155 if len(fmt):
156 h.setFormatter(formatters[fmt])
157 #temporary hack for FileHandler and MemoryHandler.
158 if klass == logging.handlers.MemoryHandler:
159 if "target" in opts:
160 target = cp.get(sectname,"target")
161 else:
162 target = ""
163 if len(target): #the target handler may not be loaded yet, so keep for later...
164 fixups.append((h, target))
165 handlers[hand] = h
166 #now all handlers are loaded, fixup inter-handler references...
167 for h, t in fixups:
168 h.setTarget(handlers[t])
169 return handlers
172 def _install_loggers(cp, handlers):
173 """Create and install loggers"""
175 # configure the root first
176 llist = cp.get("loggers", "keys")
177 llist = string.split(llist, ",")
178 llist.remove("root")
179 sectname = "logger_root"
180 root = logging.root
181 log = root
182 opts = cp.options(sectname)
183 if "level" in opts:
184 level = cp.get(sectname, "level")
185 log.setLevel(logging._levelNames[level])
186 for h in root.handlers[:]:
187 root.removeHandler(h)
188 hlist = cp.get(sectname, "handlers")
189 if len(hlist):
190 hlist = string.split(hlist, ",")
191 for hand in hlist:
192 log.addHandler(handlers[hand])
194 #and now the others...
195 #we don't want to lose the existing loggers,
196 #since other threads may have pointers to them.
197 #existing is set to contain all existing loggers,
198 #and as we go through the new configuration we
199 #remove any which are configured. At the end,
200 #what's left in existing is the set of loggers
201 #which were in the previous configuration but
202 #which are not in the new configuration.
203 existing = root.manager.loggerDict.keys()
204 #now set up the new ones...
205 for log in llist:
206 sectname = "logger_%s" % log
207 qn = cp.get(sectname, "qualname")
208 opts = cp.options(sectname)
209 if "propagate" in opts:
210 propagate = cp.getint(sectname, "propagate")
211 else:
212 propagate = 1
213 logger = logging.getLogger(qn)
214 if qn in existing:
215 existing.remove(qn)
216 if "level" in opts:
217 level = cp.get(sectname, "level")
218 logger.setLevel(logging._levelNames[level])
219 for h in logger.handlers[:]:
220 logger.removeHandler(h)
221 logger.propagate = propagate
222 logger.disabled = 0
223 hlist = cp.get(sectname, "handlers")
224 if len(hlist):
225 hlist = string.split(hlist, ",")
226 for hand in hlist:
227 logger.addHandler(handlers[hand])
229 #Disable any old loggers. There's no point deleting
230 #them as other threads may continue to hold references
231 #and by disabling them, you stop them doing any logging.
232 for log in existing:
233 root.manager.loggerDict[log].disabled = 1
236 def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
238 Start up a socket server on the specified port, and listen for new
239 configurations.
241 These will be sent as a file suitable for processing by fileConfig().
242 Returns a Thread object on which you can call start() to start the server,
243 and which you can join() when appropriate. To stop the server, call
244 stopListening().
246 if not thread:
247 raise NotImplementedError, "listen() needs threading to work"
249 class ConfigStreamHandler(StreamRequestHandler):
251 Handler for a logging configuration request.
253 It expects a completely new logging configuration and uses fileConfig
254 to install it.
256 def handle(self):
258 Handle a request.
260 Each request is expected to be a 4-byte length, packed using
261 struct.pack(">L", n), followed by the config file.
262 Uses fileConfig() to do the grunt work.
264 import tempfile
265 try:
266 conn = self.connection
267 chunk = conn.recv(4)
268 if len(chunk) == 4:
269 slen = struct.unpack(">L", chunk)[0]
270 chunk = self.connection.recv(slen)
271 while len(chunk) < slen:
272 chunk = chunk + conn.recv(slen - len(chunk))
273 #Apply new configuration. We'd like to be able to
274 #create a StringIO and pass that in, but unfortunately
275 #1.5.2 ConfigParser does not support reading file
276 #objects, only actual files. So we create a temporary
277 #file and remove it later.
278 file = tempfile.mktemp(".ini")
279 f = open(file, "w")
280 f.write(chunk)
281 f.close()
282 try:
283 fileConfig(file)
284 except (KeyboardInterrupt, SystemExit):
285 raise
286 except:
287 traceback.print_exc()
288 os.remove(file)
289 except socket.error, e:
290 if type(e.args) != types.TupleType:
291 raise
292 else:
293 errcode = e.args[0]
294 if errcode != RESET_ERROR:
295 raise
297 class ConfigSocketReceiver(ThreadingTCPServer):
299 A simple TCP socket-based logging config receiver.
302 allow_reuse_address = 1
304 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
305 handler=None):
306 ThreadingTCPServer.__init__(self, (host, port), handler)
307 logging._acquireLock()
308 self.abort = 0
309 logging._releaseLock()
310 self.timeout = 1
312 def serve_until_stopped(self):
313 import select
314 abort = 0
315 while not abort:
316 rd, wr, ex = select.select([self.socket.fileno()],
317 [], [],
318 self.timeout)
319 if rd:
320 self.handle_request()
321 logging._acquireLock()
322 abort = self.abort
323 logging._releaseLock()
325 def serve(rcvr, hdlr, port):
326 server = rcvr(port=port, handler=hdlr)
327 global _listener
328 logging._acquireLock()
329 _listener = server
330 logging._releaseLock()
331 server.serve_until_stopped()
333 return threading.Thread(target=serve,
334 args=(ConfigSocketReceiver,
335 ConfigStreamHandler, port))
337 def stopListening():
339 Stop the listening server which was created with a call to listen().
341 global _listener
342 if _listener:
343 logging._acquireLock()
344 _listener.abort = 1
345 _listener = None
346 logging._releaseLock()