blob: 41d577f69488364a53eca6e96df8ba7c1b1ef580 [file] [log] [blame]
Vinay Sajip8e628d22005-03-13 09:57:46 +00001# Copyright 2001-2005 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 Sajip326441e2004-02-20 13:16:36 +000025Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000026
27To use, simply 'import logging' and log away!
28"""
29
Thomas Wouters89f507f2006-12-13 04:49:30 +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
38from SocketServer import ThreadingTCPServer, StreamRequestHandler
39
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
Guido van Rossum31657862002-11-14 12:52:17 +000055def fileConfig(fname, defaults=None):
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 """
68 import ConfigParser
69
Guido van Rossum31657862002-11-14 12:52:17 +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()
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000082 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000083 # Handlers add themselves to logging._handlers
84 handlers = _install_handlers(cp, formatters)
85 _install_loggers(cp, handlers)
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:
Thomas Wouters89f507f2006-12-13 04:49:30 +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:
Thomas Wouters89f507f2006-12-13 04:49:30 +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 = ""
149 klass = eval(klass, vars(logging))
150 args = cp.get(sectname, "args")
151 args = eval(args, vars(logging))
Neal Norwitzd9108552006-03-17 08:00:19 +0000152 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000153 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
171
172
173def _install_loggers(cp, handlers):
174 """Create and install loggers"""
175
176 # configure the root first
177 llist = cp.get("loggers", "keys")
178 llist = string.split(llist, ",")
Thomas Wouters89f507f2006-12-13 04:49:30 +0000179 llist = map(lambda x: string.strip(x), llist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000180 llist.remove("root")
181 sectname = "logger_root"
182 root = logging.root
183 log = root
184 opts = cp.options(sectname)
185 if "level" in opts:
186 level = cp.get(sectname, "level")
187 log.setLevel(logging._levelNames[level])
188 for h in root.handlers[:]:
189 root.removeHandler(h)
190 hlist = cp.get(sectname, "handlers")
191 if len(hlist):
192 hlist = string.split(hlist, ",")
193 for hand in hlist:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000194 log.addHandler(handlers[string.strip(hand)])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000195
196 #and now the others...
197 #we don't want to lose the existing loggers,
198 #since other threads may have pointers to them.
199 #existing is set to contain all existing loggers,
200 #and as we go through the new configuration we
201 #remove any which are configured. At the end,
202 #what's left in existing is the set of loggers
203 #which were in the previous configuration but
204 #which are not in the new configuration.
205 existing = root.manager.loggerDict.keys()
206 #now set up the new ones...
207 for log in llist:
208 sectname = "logger_%s" % log
209 qn = cp.get(sectname, "qualname")
210 opts = cp.options(sectname)
211 if "propagate" in opts:
212 propagate = cp.getint(sectname, "propagate")
213 else:
214 propagate = 1
215 logger = logging.getLogger(qn)
216 if qn in existing:
217 existing.remove(qn)
218 if "level" in opts:
219 level = cp.get(sectname, "level")
220 logger.setLevel(logging._levelNames[level])
221 for h in logger.handlers[:]:
222 logger.removeHandler(h)
223 logger.propagate = propagate
224 logger.disabled = 0
225 hlist = cp.get(sectname, "handlers")
226 if len(hlist):
227 hlist = string.split(hlist, ",")
228 for hand in hlist:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000229 logger.addHandler(handlers[string.strip(hand)])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000230
231 #Disable any old loggers. There's no point deleting
232 #them as other threads may continue to hold references
233 #and by disabling them, you stop them doing any logging.
234 for log in existing:
235 root.manager.loggerDict[log].disabled = 1
236
237
Guido van Rossum57102f82002-11-13 16:15:58 +0000238def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
239 """
240 Start up a socket server on the specified port, and listen for new
241 configurations.
242
243 These will be sent as a file suitable for processing by fileConfig().
244 Returns a Thread object on which you can call start() to start the server,
245 and which you can join() when appropriate. To stop the server, call
246 stopListening().
247 """
248 if not thread:
249 raise NotImplementedError, "listen() needs threading to work"
250
251 class ConfigStreamHandler(StreamRequestHandler):
252 """
253 Handler for a logging configuration request.
254
255 It expects a completely new logging configuration and uses fileConfig
256 to install it.
257 """
258 def handle(self):
259 """
260 Handle a request.
261
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000262 Each request is expected to be a 4-byte length, packed using
263 struct.pack(">L", n), followed by the config file.
264 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000265 """
266 import tempfile
267 try:
268 conn = self.connection
269 chunk = conn.recv(4)
270 if len(chunk) == 4:
271 slen = struct.unpack(">L", chunk)[0]
272 chunk = self.connection.recv(slen)
273 while len(chunk) < slen:
274 chunk = chunk + conn.recv(slen - len(chunk))
275 #Apply new configuration. We'd like to be able to
276 #create a StringIO and pass that in, but unfortunately
277 #1.5.2 ConfigParser does not support reading file
278 #objects, only actual files. So we create a temporary
279 #file and remove it later.
280 file = tempfile.mktemp(".ini")
281 f = open(file, "w")
282 f.write(chunk)
283 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000284 try:
285 fileConfig(file)
286 except (KeyboardInterrupt, SystemExit):
287 raise
288 except:
289 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000290 os.remove(file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000291 except socket.error as e:
Guido van Rossum57102f82002-11-13 16:15:58 +0000292 if type(e.args) != types.TupleType:
293 raise
294 else:
295 errcode = e.args[0]
296 if errcode != RESET_ERROR:
297 raise
298
299 class ConfigSocketReceiver(ThreadingTCPServer):
300 """
301 A simple TCP socket-based logging config receiver.
302 """
303
304 allow_reuse_address = 1
305
306 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000307 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000308 ThreadingTCPServer.__init__(self, (host, port), handler)
309 logging._acquireLock()
310 self.abort = 0
311 logging._releaseLock()
312 self.timeout = 1
313
314 def serve_until_stopped(self):
315 import select
316 abort = 0
317 while not abort:
318 rd, wr, ex = select.select([self.socket.fileno()],
319 [], [],
320 self.timeout)
321 if rd:
322 self.handle_request()
323 logging._acquireLock()
324 abort = self.abort
325 logging._releaseLock()
326
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000327 def serve(rcvr, hdlr, port):
328 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000329 global _listener
330 logging._acquireLock()
331 _listener = server
332 logging._releaseLock()
333 server.serve_until_stopped()
334
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000335 return threading.Thread(target=serve,
336 args=(ConfigSocketReceiver,
337 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000338
339def stopListening():
340 """
341 Stop the listening server which was created with a call to listen().
342 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000343 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000344 if _listener:
345 logging._acquireLock()
346 _listener.abort = 1
347 _listener = None
348 logging._releaseLock()