blob: 5adfe4dd21d80dccde8b7ff8ac7ae4aef2b4773e [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
Vinay Sajip8e628d22005-03-13 09:57:46 +000030import sys, logging, logging.handlers, string, socket, struct, os, traceback
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()
82 # Handlers add themselves to logging._handlers
83 handlers = _install_handlers(cp, formatters)
84 _install_loggers(cp, handlers)
Guido van Rossum57102f82002-11-13 16:15:58 +000085 finally:
86 logging._releaseLock()
87
Vinay Sajip989b69a2006-01-16 21:28:37 +000088
Vinay Sajip7a7160b2006-01-20 18:28:03 +000089def _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
102
103
Vinay Sajip989b69a2006-01-16 21:28:37 +0000104def _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
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000122 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)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000128 formatters[form] = f
129 return formatters
130
131
132def _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
170
171
172def _install_loggers(cp, handlers):
173 """Create and install loggers"""
174
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])
193
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])
228
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
234
235
Guido van Rossum57102f82002-11-13 16:15:58 +0000236def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
237 """
238 Start up a socket server on the specified port, and listen for new
239 configurations.
240
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().
245 """
246 if not thread:
247 raise NotImplementedError, "listen() needs threading to work"
248
249 class ConfigStreamHandler(StreamRequestHandler):
250 """
251 Handler for a logging configuration request.
252
253 It expects a completely new logging configuration and uses fileConfig
254 to install it.
255 """
256 def handle(self):
257 """
258 Handle a request.
259
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000260 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.
Guido van Rossum57102f82002-11-13 16:15:58 +0000263 """
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()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000282 try:
283 fileConfig(file)
284 except (KeyboardInterrupt, SystemExit):
285 raise
286 except:
287 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000288 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
296
297 class ConfigSocketReceiver(ThreadingTCPServer):
298 """
299 A simple TCP socket-based logging config receiver.
300 """
301
302 allow_reuse_address = 1
303
304 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000305 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000306 ThreadingTCPServer.__init__(self, (host, port), handler)
307 logging._acquireLock()
308 self.abort = 0
309 logging._releaseLock()
310 self.timeout = 1
311
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()
324
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000325 def serve(rcvr, hdlr, port):
326 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000327 global _listener
328 logging._acquireLock()
329 _listener = server
330 logging._releaseLock()
331 server.serve_until_stopped()
332
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000333 return threading.Thread(target=serve,
334 args=(ConfigSocketReceiver,
335 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000336
337def stopListening():
338 """
339 Stop the listening server which was created with a call to listen().
340 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000341 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000342 if _listener:
343 logging._acquireLock()
344 _listener.abort = 1
345 _listener = None
346 logging._releaseLock()