blob: 6d3daa269375d08d3ab16db633aeb538329e7610 [file] [log] [blame]
Vinay Sajip95dd03b2007-11-11 14:27:30 +00001# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +00002#
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 Rossum57102f82002-11-13 16:15:58 +000016
17"""
Vinay Sajip3f742842004-02-28 16:07:46 +000018Configuration functions for the logging package for Python. The core package
19is based on PEP 282 and comments thereto in comp.lang.python, and influenced
20by Apache's log4j system.
Guido van Rossum57102f82002-11-13 16:15:58 +000021
22Should work under Python versions >= 1.5.2, except that source line
Vinay Sajip326441e2004-02-20 13:16:36 +000023information is not available unless 'sys._getframe()' is.
Guido van Rossum57102f82002-11-13 16:15:58 +000024
Vinay Sajip95dd03b2007-11-11 14:27:30 +000025Copyright (C) 2001-2007 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000026
27To use, simply 'import logging' and log away!
28"""
29
Georg Brandl4e933132006-09-06 20:05:58 +000030import sys, logging, logging.handlers, string, socket, struct, os, traceback, types
Vinay Sajip612df8e2005-02-18 11:54:46 +000031
32try:
33 import thread
34 import threading
35except ImportError:
36 thread = None
Guido van Rossum57102f82002-11-13 16:15:58 +000037
Georg Brandle152a772008-05-24 18:31:28 +000038from SocketServer import ThreadingTCPServer, StreamRequestHandler
Guido van Rossum57102f82002-11-13 16:15:58 +000039
40
41DEFAULT_LOGGING_CONFIG_PORT = 9030
42
Vinay Sajip326441e2004-02-20 13:16:36 +000043if sys.platform == "win32":
44 RESET_ERROR = 10054 #WSAECONNRESET
45else:
46 RESET_ERROR = 104 #ECONNRESET
47
Guido van Rossum57102f82002-11-13 16:15:58 +000048#
49# The following code implements a socket listener for on-the-fly
50# reconfiguration of logging.
51#
52# _listener holds the server object doing the listening
53_listener = None
54
Vinay Sajip5f7b97d2008-06-19 22:40:17 +000055def fileConfig(fname, defaults=None, disable_existing_loggers=1):
Guido van Rossum57102f82002-11-13 16:15:58 +000056 """
57 Read the logging configuration from a ConfigParser-format file.
58
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 """
Georg Brandl392c6fc2008-05-25 07:25:25 +000068 import ConfigParser
Guido van Rossum57102f82002-11-13 16:15:58 +000069
Georg Brandl392c6fc2008-05-25 07:25:25 +000070 cp = ConfigParser.ConfigParser(defaults)
Guido van Rossum57102f82002-11-13 16:15:58 +000071 if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
72 cp.readfp(fname)
73 else:
74 cp.read(fname)
Vinay Sajip989b69a2006-01-16 21:28:37 +000075
76 formatters = _create_formatters(cp)
77
78 # critical section
Guido van Rossum57102f82002-11-13 16:15:58 +000079 logging._acquireLock()
80 try:
Vinay Sajip989b69a2006-01-16 21:28:37 +000081 logging._handlers.clear()
Georg Brandlf3e30422006-08-12 08:32:02 +000082 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000083 # Handlers add themselves to logging._handlers
84 handlers = _install_handlers(cp, formatters)
Vinay Sajip5f7b97d2008-06-19 22:40:17 +000085 _install_loggers(cp, handlers, disable_existing_loggers)
Guido van Rossum57102f82002-11-13 16:15:58 +000086 finally:
87 logging._releaseLock()
88
Vinay Sajip989b69a2006-01-16 21:28:37 +000089
Vinay Sajip7a7160b2006-01-20 18:28:03 +000090def _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
103
104
Vinay Sajip989b69a2006-01-16 21:28:37 +0000105def _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:
Vinay Sajip66a17262006-12-11 14:26:23 +0000113 sectname = "formatter_%s" % string.strip(form)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000114 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
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000123 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)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000129 formatters[form] = f
130 return formatters
131
132
133def _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:
Vinay Sajip66a17262006-12-11 14:26:23 +0000142 sectname = "handler_%s" % string.strip(hand)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000143 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 = ""
Vinay Sajipbc7e34f2008-07-18 08:59:06 +0000149 try:
150 klass = eval(klass, vars(logging))
151 except (AttributeError, NameError):
152 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000153 args = cp.get(sectname, "args")
154 args = eval(args, vars(logging))
155 h = apply(klass, args)
156 if "level" in opts:
157 level = cp.get(sectname, "level")
158 h.setLevel(logging._levelNames[level])
159 if len(fmt):
160 h.setFormatter(formatters[fmt])
Vinay Sajip5ff71712008-06-29 21:25:28 +0000161 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000162 if "target" in opts:
163 target = cp.get(sectname,"target")
164 else:
165 target = ""
166 if len(target): #the target handler may not be loaded yet, so keep for later...
167 fixups.append((h, target))
168 handlers[hand] = h
169 #now all handlers are loaded, fixup inter-handler references...
170 for h, t in fixups:
171 h.setTarget(handlers[t])
172 return handlers
173
174
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000175def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000176 """Create and install loggers"""
177
178 # configure the root first
179 llist = cp.get("loggers", "keys")
180 llist = string.split(llist, ",")
Vinay Sajip66a17262006-12-11 14:26:23 +0000181 llist = map(lambda x: string.strip(x), llist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000182 llist.remove("root")
183 sectname = "logger_root"
184 root = logging.root
185 log = root
186 opts = cp.options(sectname)
187 if "level" in opts:
188 level = cp.get(sectname, "level")
189 log.setLevel(logging._levelNames[level])
190 for h in root.handlers[:]:
191 root.removeHandler(h)
192 hlist = cp.get(sectname, "handlers")
193 if len(hlist):
194 hlist = string.split(hlist, ",")
195 for hand in hlist:
Vinay Sajip66a17262006-12-11 14:26:23 +0000196 log.addHandler(handlers[string.strip(hand)])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000197
198 #and now the others...
199 #we don't want to lose the existing loggers,
200 #since other threads may have pointers to them.
201 #existing is set to contain all existing loggers,
202 #and as we go through the new configuration we
203 #remove any which are configured. At the end,
204 #what's left in existing is the set of loggers
205 #which were in the previous configuration but
206 #which are not in the new configuration.
207 existing = root.manager.loggerDict.keys()
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000208 #The list needs to be sorted so that we can
209 #avoid disabling child loggers of explicitly
210 #named loggers. With a sorted list it is easier
211 #to find the child loggers.
212 existing.sort()
213 #We'll keep the list of existing loggers
214 #which are children of named loggers here...
215 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000216 #now set up the new ones...
217 for log in llist:
218 sectname = "logger_%s" % log
219 qn = cp.get(sectname, "qualname")
220 opts = cp.options(sectname)
221 if "propagate" in opts:
222 propagate = cp.getint(sectname, "propagate")
223 else:
224 propagate = 1
225 logger = logging.getLogger(qn)
226 if qn in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000227 i = existing.index(qn)
228 prefixed = qn + "."
229 pflen = len(prefixed)
230 num_existing = len(existing)
231 i = i + 1 # look at the entry after qn
232 while (i < num_existing) and (existing[i][:pflen] == prefixed):
233 child_loggers.append(existing[i])
234 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000235 existing.remove(qn)
236 if "level" in opts:
237 level = cp.get(sectname, "level")
238 logger.setLevel(logging._levelNames[level])
239 for h in logger.handlers[:]:
240 logger.removeHandler(h)
241 logger.propagate = propagate
242 logger.disabled = 0
243 hlist = cp.get(sectname, "handlers")
244 if len(hlist):
245 hlist = string.split(hlist, ",")
246 for hand in hlist:
Vinay Sajip66a17262006-12-11 14:26:23 +0000247 logger.addHandler(handlers[string.strip(hand)])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000248
249 #Disable any old loggers. There's no point deleting
250 #them as other threads may continue to hold references
251 #and by disabling them, you stop them doing any logging.
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000252 #However, don't disable children of named loggers, as that's
253 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000254 for log in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000255 logger = root.manager.loggerDict[log]
256 if log in child_loggers:
257 logger.level = logging.NOTSET
258 logger.handlers = []
259 logger.propagate = 1
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000260 elif disable_existing_loggers:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000261 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000262
263
Guido van Rossum57102f82002-11-13 16:15:58 +0000264def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
265 """
266 Start up a socket server on the specified port, and listen for new
267 configurations.
268
269 These will be sent as a file suitable for processing by fileConfig().
270 Returns a Thread object on which you can call start() to start the server,
271 and which you can join() when appropriate. To stop the server, call
272 stopListening().
273 """
274 if not thread:
275 raise NotImplementedError, "listen() needs threading to work"
276
277 class ConfigStreamHandler(StreamRequestHandler):
278 """
279 Handler for a logging configuration request.
280
281 It expects a completely new logging configuration and uses fileConfig
282 to install it.
283 """
284 def handle(self):
285 """
286 Handle a request.
287
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000288 Each request is expected to be a 4-byte length, packed using
289 struct.pack(">L", n), followed by the config file.
290 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000291 """
292 import tempfile
293 try:
294 conn = self.connection
295 chunk = conn.recv(4)
296 if len(chunk) == 4:
297 slen = struct.unpack(">L", chunk)[0]
298 chunk = self.connection.recv(slen)
299 while len(chunk) < slen:
300 chunk = chunk + conn.recv(slen - len(chunk))
301 #Apply new configuration. We'd like to be able to
302 #create a StringIO and pass that in, but unfortunately
303 #1.5.2 ConfigParser does not support reading file
304 #objects, only actual files. So we create a temporary
305 #file and remove it later.
306 file = tempfile.mktemp(".ini")
307 f = open(file, "w")
308 f.write(chunk)
309 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000310 try:
311 fileConfig(file)
312 except (KeyboardInterrupt, SystemExit):
313 raise
314 except:
315 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000316 os.remove(file)
317 except socket.error, e:
318 if type(e.args) != types.TupleType:
319 raise
320 else:
321 errcode = e.args[0]
322 if errcode != RESET_ERROR:
323 raise
324
325 class ConfigSocketReceiver(ThreadingTCPServer):
326 """
327 A simple TCP socket-based logging config receiver.
328 """
329
330 allow_reuse_address = 1
331
332 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000333 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000334 ThreadingTCPServer.__init__(self, (host, port), handler)
335 logging._acquireLock()
336 self.abort = 0
337 logging._releaseLock()
338 self.timeout = 1
339
340 def serve_until_stopped(self):
341 import select
342 abort = 0
343 while not abort:
344 rd, wr, ex = select.select([self.socket.fileno()],
345 [], [],
346 self.timeout)
347 if rd:
348 self.handle_request()
349 logging._acquireLock()
350 abort = self.abort
351 logging._releaseLock()
352
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000353 def serve(rcvr, hdlr, port):
354 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000355 global _listener
356 logging._acquireLock()
357 _listener = server
358 logging._releaseLock()
359 server.serve_until_stopped()
360
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000361 return threading.Thread(target=serve,
362 args=(ConfigSocketReceiver,
363 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000364
365def stopListening():
366 """
367 Stop the listening server which was created with a call to listen().
368 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000369 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000370 if _listener:
371 logging._acquireLock()
372 _listener.abort = 1
373 _listener = None
374 logging._releaseLock()