blob: 3017ae981c9139d45f770799ea2b432fc55308d8 [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 Sajip6a2fd812008-09-03 09:20:05 +000025Copyright (C) 2001-2008 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
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000104def _strip_spaces(alist):
105 return map(lambda x: string.strip(x), alist)
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000106
Vinay Sajip989b69a2006-01-16 21:28:37 +0000107def _create_formatters(cp):
108 """Create and return formatters"""
109 flist = cp.get("formatters", "keys")
110 if not len(flist):
111 return {}
112 flist = string.split(flist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000113 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000114 formatters = {}
115 for form in flist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000116 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000117 opts = cp.options(sectname)
118 if "format" in opts:
119 fs = cp.get(sectname, "format", 1)
120 else:
121 fs = None
122 if "datefmt" in opts:
123 dfs = cp.get(sectname, "datefmt", 1)
124 else:
125 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000126 c = logging.Formatter
127 if "class" in opts:
128 class_name = cp.get(sectname, "class")
129 if class_name:
130 c = _resolve(class_name)
131 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000132 formatters[form] = f
133 return formatters
134
135
136def _install_handlers(cp, formatters):
137 """Install and return handlers"""
138 hlist = cp.get("handlers", "keys")
139 if not len(hlist):
140 return {}
141 hlist = string.split(hlist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000142 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000143 handlers = {}
144 fixups = [] #for inter-handler references
145 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000146 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000147 klass = cp.get(sectname, "class")
148 opts = cp.options(sectname)
149 if "formatter" in opts:
150 fmt = cp.get(sectname, "formatter")
151 else:
152 fmt = ""
Vinay Sajipbc7e34f2008-07-18 08:59:06 +0000153 try:
154 klass = eval(klass, vars(logging))
155 except (AttributeError, NameError):
156 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000157 args = cp.get(sectname, "args")
158 args = eval(args, vars(logging))
Brett Cannone6bfe802008-08-04 00:09:43 +0000159 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000160 if "level" in opts:
161 level = cp.get(sectname, "level")
162 h.setLevel(logging._levelNames[level])
163 if len(fmt):
164 h.setFormatter(formatters[fmt])
Vinay Sajip5ff71712008-06-29 21:25:28 +0000165 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000166 if "target" in opts:
167 target = cp.get(sectname,"target")
168 else:
169 target = ""
170 if len(target): #the target handler may not be loaded yet, so keep for later...
171 fixups.append((h, target))
172 handlers[hand] = h
173 #now all handlers are loaded, fixup inter-handler references...
174 for h, t in fixups:
175 h.setTarget(handlers[t])
176 return handlers
177
178
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000179def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000180 """Create and install loggers"""
181
182 # configure the root first
183 llist = cp.get("loggers", "keys")
184 llist = string.split(llist, ",")
Vinay Sajip66a17262006-12-11 14:26:23 +0000185 llist = map(lambda x: string.strip(x), llist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000186 llist.remove("root")
187 sectname = "logger_root"
188 root = logging.root
189 log = root
190 opts = cp.options(sectname)
191 if "level" in opts:
192 level = cp.get(sectname, "level")
193 log.setLevel(logging._levelNames[level])
194 for h in root.handlers[:]:
195 root.removeHandler(h)
196 hlist = cp.get(sectname, "handlers")
197 if len(hlist):
198 hlist = string.split(hlist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000199 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000200 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000201 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000202
203 #and now the others...
204 #we don't want to lose the existing loggers,
205 #since other threads may have pointers to them.
206 #existing is set to contain all existing loggers,
207 #and as we go through the new configuration we
208 #remove any which are configured. At the end,
209 #what's left in existing is the set of loggers
210 #which were in the previous configuration but
211 #which are not in the new configuration.
212 existing = root.manager.loggerDict.keys()
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000213 #The list needs to be sorted so that we can
214 #avoid disabling child loggers of explicitly
215 #named loggers. With a sorted list it is easier
216 #to find the child loggers.
217 existing.sort()
218 #We'll keep the list of existing loggers
219 #which are children of named loggers here...
220 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000221 #now set up the new ones...
222 for log in llist:
223 sectname = "logger_%s" % log
224 qn = cp.get(sectname, "qualname")
225 opts = cp.options(sectname)
226 if "propagate" in opts:
227 propagate = cp.getint(sectname, "propagate")
228 else:
229 propagate = 1
230 logger = logging.getLogger(qn)
231 if qn in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000232 i = existing.index(qn)
233 prefixed = qn + "."
234 pflen = len(prefixed)
235 num_existing = len(existing)
236 i = i + 1 # look at the entry after qn
237 while (i < num_existing) and (existing[i][:pflen] == prefixed):
238 child_loggers.append(existing[i])
239 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000240 existing.remove(qn)
241 if "level" in opts:
242 level = cp.get(sectname, "level")
243 logger.setLevel(logging._levelNames[level])
244 for h in logger.handlers[:]:
245 logger.removeHandler(h)
246 logger.propagate = propagate
247 logger.disabled = 0
248 hlist = cp.get(sectname, "handlers")
249 if len(hlist):
250 hlist = string.split(hlist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000251 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000252 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000253 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000254
255 #Disable any old loggers. There's no point deleting
256 #them as other threads may continue to hold references
257 #and by disabling them, you stop them doing any logging.
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000258 #However, don't disable children of named loggers, as that's
259 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000260 for log in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000261 logger = root.manager.loggerDict[log]
262 if log in child_loggers:
263 logger.level = logging.NOTSET
264 logger.handlers = []
265 logger.propagate = 1
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000266 elif disable_existing_loggers:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000267 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000268
269
Guido van Rossum57102f82002-11-13 16:15:58 +0000270def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
271 """
272 Start up a socket server on the specified port, and listen for new
273 configurations.
274
275 These will be sent as a file suitable for processing by fileConfig().
276 Returns a Thread object on which you can call start() to start the server,
277 and which you can join() when appropriate. To stop the server, call
278 stopListening().
279 """
280 if not thread:
281 raise NotImplementedError, "listen() needs threading to work"
282
283 class ConfigStreamHandler(StreamRequestHandler):
284 """
285 Handler for a logging configuration request.
286
287 It expects a completely new logging configuration and uses fileConfig
288 to install it.
289 """
290 def handle(self):
291 """
292 Handle a request.
293
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000294 Each request is expected to be a 4-byte length, packed using
295 struct.pack(">L", n), followed by the config file.
296 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000297 """
298 import tempfile
299 try:
300 conn = self.connection
301 chunk = conn.recv(4)
302 if len(chunk) == 4:
303 slen = struct.unpack(">L", chunk)[0]
304 chunk = self.connection.recv(slen)
305 while len(chunk) < slen:
306 chunk = chunk + conn.recv(slen - len(chunk))
307 #Apply new configuration. We'd like to be able to
308 #create a StringIO and pass that in, but unfortunately
309 #1.5.2 ConfigParser does not support reading file
310 #objects, only actual files. So we create a temporary
311 #file and remove it later.
312 file = tempfile.mktemp(".ini")
313 f = open(file, "w")
314 f.write(chunk)
315 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000316 try:
317 fileConfig(file)
318 except (KeyboardInterrupt, SystemExit):
319 raise
320 except:
321 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000322 os.remove(file)
323 except socket.error, e:
324 if type(e.args) != types.TupleType:
325 raise
326 else:
327 errcode = e.args[0]
328 if errcode != RESET_ERROR:
329 raise
330
331 class ConfigSocketReceiver(ThreadingTCPServer):
332 """
333 A simple TCP socket-based logging config receiver.
334 """
335
336 allow_reuse_address = 1
337
338 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000339 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000340 ThreadingTCPServer.__init__(self, (host, port), handler)
341 logging._acquireLock()
342 self.abort = 0
343 logging._releaseLock()
344 self.timeout = 1
345
346 def serve_until_stopped(self):
347 import select
348 abort = 0
349 while not abort:
350 rd, wr, ex = select.select([self.socket.fileno()],
351 [], [],
352 self.timeout)
353 if rd:
354 self.handle_request()
355 logging._acquireLock()
356 abort = self.abort
357 logging._releaseLock()
358
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000359 def serve(rcvr, hdlr, port):
360 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000361 global _listener
362 logging._acquireLock()
363 _listener = server
364 logging._releaseLock()
365 server.serve_until_stopped()
366
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000367 return threading.Thread(target=serve,
368 args=(ConfigSocketReceiver,
369 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000370
371def stopListening():
372 """
373 Stop the listening server which was created with a call to listen().
374 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000375 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000376 if _listener:
377 logging._acquireLock()
378 _listener.abort = 1
379 _listener = None
380 logging._releaseLock()