blob: eb2c2484e21b44cd59b6c14fc9894285307c841b [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 Sajip8a435c42010-07-20 20:18:14 +0000107def _encoded(s):
108 return s if isinstance(s, str) else s.encode('utf-8')
109
Vinay Sajip989b69a2006-01-16 21:28:37 +0000110def _create_formatters(cp):
111 """Create and return formatters"""
112 flist = cp.get("formatters", "keys")
113 if not len(flist):
114 return {}
115 flist = string.split(flist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000116 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000117 formatters = {}
118 for form in flist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000119 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000120 opts = cp.options(sectname)
121 if "format" in opts:
122 fs = cp.get(sectname, "format", 1)
123 else:
124 fs = None
125 if "datefmt" in opts:
126 dfs = cp.get(sectname, "datefmt", 1)
127 else:
128 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000129 c = logging.Formatter
130 if "class" in opts:
131 class_name = cp.get(sectname, "class")
132 if class_name:
133 c = _resolve(class_name)
134 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000135 formatters[form] = f
136 return formatters
137
138
139def _install_handlers(cp, formatters):
140 """Install and return handlers"""
141 hlist = cp.get("handlers", "keys")
142 if not len(hlist):
143 return {}
144 hlist = string.split(hlist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000145 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000146 handlers = {}
147 fixups = [] #for inter-handler references
148 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000149 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000150 klass = cp.get(sectname, "class")
151 opts = cp.options(sectname)
152 if "formatter" in opts:
153 fmt = cp.get(sectname, "formatter")
154 else:
155 fmt = ""
Vinay Sajipbc7e34f2008-07-18 08:59:06 +0000156 try:
157 klass = eval(klass, vars(logging))
158 except (AttributeError, NameError):
159 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000160 args = cp.get(sectname, "args")
161 args = eval(args, vars(logging))
Brett Cannone6bfe802008-08-04 00:09:43 +0000162 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000163 if "level" in opts:
164 level = cp.get(sectname, "level")
165 h.setLevel(logging._levelNames[level])
166 if len(fmt):
167 h.setFormatter(formatters[fmt])
Vinay Sajip5ff71712008-06-29 21:25:28 +0000168 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000169 if "target" in opts:
170 target = cp.get(sectname,"target")
171 else:
172 target = ""
173 if len(target): #the target handler may not be loaded yet, so keep for later...
174 fixups.append((h, target))
175 handlers[hand] = h
176 #now all handlers are loaded, fixup inter-handler references...
177 for h, t in fixups:
178 h.setTarget(handlers[t])
179 return handlers
180
181
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000182def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000183 """Create and install loggers"""
184
185 # configure the root first
186 llist = cp.get("loggers", "keys")
187 llist = string.split(llist, ",")
Vinay Sajip66a17262006-12-11 14:26:23 +0000188 llist = map(lambda x: string.strip(x), llist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000189 llist.remove("root")
190 sectname = "logger_root"
191 root = logging.root
192 log = root
193 opts = cp.options(sectname)
194 if "level" in opts:
195 level = cp.get(sectname, "level")
196 log.setLevel(logging._levelNames[level])
197 for h in root.handlers[:]:
198 root.removeHandler(h)
199 hlist = cp.get(sectname, "handlers")
200 if len(hlist):
201 hlist = string.split(hlist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000202 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000203 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000204 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000205
206 #and now the others...
207 #we don't want to lose the existing loggers,
208 #since other threads may have pointers to them.
209 #existing is set to contain all existing loggers,
210 #and as we go through the new configuration we
211 #remove any which are configured. At the end,
212 #what's left in existing is the set of loggers
213 #which were in the previous configuration but
214 #which are not in the new configuration.
215 existing = root.manager.loggerDict.keys()
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000216 #The list needs to be sorted so that we can
217 #avoid disabling child loggers of explicitly
218 #named loggers. With a sorted list it is easier
219 #to find the child loggers.
Vinay Sajip8a435c42010-07-20 20:18:14 +0000220 existing.sort(key=_encoded)
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000221 #We'll keep the list of existing loggers
222 #which are children of named loggers here...
223 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000224 #now set up the new ones...
225 for log in llist:
226 sectname = "logger_%s" % log
227 qn = cp.get(sectname, "qualname")
228 opts = cp.options(sectname)
229 if "propagate" in opts:
230 propagate = cp.getint(sectname, "propagate")
231 else:
232 propagate = 1
233 logger = logging.getLogger(qn)
234 if qn in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000235 i = existing.index(qn)
236 prefixed = qn + "."
237 pflen = len(prefixed)
238 num_existing = len(existing)
239 i = i + 1 # look at the entry after qn
240 while (i < num_existing) and (existing[i][:pflen] == prefixed):
241 child_loggers.append(existing[i])
242 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000243 existing.remove(qn)
244 if "level" in opts:
245 level = cp.get(sectname, "level")
246 logger.setLevel(logging._levelNames[level])
247 for h in logger.handlers[:]:
248 logger.removeHandler(h)
249 logger.propagate = propagate
250 logger.disabled = 0
251 hlist = cp.get(sectname, "handlers")
252 if len(hlist):
253 hlist = string.split(hlist, ",")
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000254 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000255 for hand in hlist:
Vinay Sajip6a2fd812008-09-03 09:20:05 +0000256 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000257
258 #Disable any old loggers. There's no point deleting
259 #them as other threads may continue to hold references
260 #and by disabling them, you stop them doing any logging.
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000261 #However, don't disable children of named loggers, as that's
262 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000263 for log in existing:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000264 logger = root.manager.loggerDict[log]
265 if log in child_loggers:
266 logger.level = logging.NOTSET
267 logger.handlers = []
268 logger.propagate = 1
Vinay Sajip5f7b97d2008-06-19 22:40:17 +0000269 elif disable_existing_loggers:
Vinay Sajip95dd03b2007-11-11 14:27:30 +0000270 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000271
272
Guido van Rossum57102f82002-11-13 16:15:58 +0000273def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
274 """
275 Start up a socket server on the specified port, and listen for new
276 configurations.
277
278 These will be sent as a file suitable for processing by fileConfig().
279 Returns a Thread object on which you can call start() to start the server,
280 and which you can join() when appropriate. To stop the server, call
281 stopListening().
282 """
283 if not thread:
284 raise NotImplementedError, "listen() needs threading to work"
285
286 class ConfigStreamHandler(StreamRequestHandler):
287 """
288 Handler for a logging configuration request.
289
290 It expects a completely new logging configuration and uses fileConfig
291 to install it.
292 """
293 def handle(self):
294 """
295 Handle a request.
296
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000297 Each request is expected to be a 4-byte length, packed using
298 struct.pack(">L", n), followed by the config file.
299 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000300 """
301 import tempfile
302 try:
303 conn = self.connection
304 chunk = conn.recv(4)
305 if len(chunk) == 4:
306 slen = struct.unpack(">L", chunk)[0]
307 chunk = self.connection.recv(slen)
308 while len(chunk) < slen:
309 chunk = chunk + conn.recv(slen - len(chunk))
310 #Apply new configuration. We'd like to be able to
311 #create a StringIO and pass that in, but unfortunately
312 #1.5.2 ConfigParser does not support reading file
313 #objects, only actual files. So we create a temporary
314 #file and remove it later.
315 file = tempfile.mktemp(".ini")
316 f = open(file, "w")
317 f.write(chunk)
318 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000319 try:
320 fileConfig(file)
321 except (KeyboardInterrupt, SystemExit):
322 raise
323 except:
324 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000325 os.remove(file)
326 except socket.error, e:
327 if type(e.args) != types.TupleType:
328 raise
329 else:
330 errcode = e.args[0]
331 if errcode != RESET_ERROR:
332 raise
333
334 class ConfigSocketReceiver(ThreadingTCPServer):
335 """
336 A simple TCP socket-based logging config receiver.
337 """
338
339 allow_reuse_address = 1
340
341 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000342 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000343 ThreadingTCPServer.__init__(self, (host, port), handler)
344 logging._acquireLock()
345 self.abort = 0
346 logging._releaseLock()
347 self.timeout = 1
348
349 def serve_until_stopped(self):
350 import select
351 abort = 0
352 while not abort:
353 rd, wr, ex = select.select([self.socket.fileno()],
354 [], [],
355 self.timeout)
356 if rd:
357 self.handle_request()
358 logging._acquireLock()
359 abort = self.abort
360 logging._releaseLock()
361
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000362 def serve(rcvr, hdlr, port):
363 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000364 global _listener
365 logging._acquireLock()
366 _listener = server
367 logging._releaseLock()
368 server.serve_until_stopped()
369
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000370 return threading.Thread(target=serve,
371 args=(ConfigSocketReceiver,
372 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000373
374def stopListening():
375 """
376 Stop the listening server which was created with a call to listen().
377 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000378 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000379 if _listener:
380 logging._acquireLock()
381 _listener.abort = 1
382 _listener = None
383 logging._releaseLock()