blob: 37d87750c7dd99f5d79c2a913f851c928c43939f [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
89def _create_formatters(cp):
90 """Create and return formatters"""
91 flist = cp.get("formatters", "keys")
92 if not len(flist):
93 return {}
94 flist = string.split(flist, ",")
95 formatters = {}
96 for form in flist:
97 sectname = "formatter_%s" % form
98 opts = cp.options(sectname)
99 if "format" in opts:
100 fs = cp.get(sectname, "format", 1)
101 else:
102 fs = None
103 if "datefmt" in opts:
104 dfs = cp.get(sectname, "datefmt", 1)
105 else:
106 dfs = None
107 f = logging.Formatter(fs, dfs)
108 formatters[form] = f
109 return formatters
110
111
112def _install_handlers(cp, formatters):
113 """Install and return handlers"""
114 hlist = cp.get("handlers", "keys")
115 if not len(hlist):
116 return {}
117 hlist = string.split(hlist, ",")
118 handlers = {}
119 fixups = [] #for inter-handler references
120 for hand in hlist:
121 sectname = "handler_%s" % hand
122 klass = cp.get(sectname, "class")
123 opts = cp.options(sectname)
124 if "formatter" in opts:
125 fmt = cp.get(sectname, "formatter")
126 else:
127 fmt = ""
128 klass = eval(klass, vars(logging))
129 args = cp.get(sectname, "args")
130 args = eval(args, vars(logging))
131 h = apply(klass, args)
132 if "level" in opts:
133 level = cp.get(sectname, "level")
134 h.setLevel(logging._levelNames[level])
135 if len(fmt):
136 h.setFormatter(formatters[fmt])
137 #temporary hack for FileHandler and MemoryHandler.
138 if klass == logging.handlers.MemoryHandler:
139 if "target" in opts:
140 target = cp.get(sectname,"target")
141 else:
142 target = ""
143 if len(target): #the target handler may not be loaded yet, so keep for later...
144 fixups.append((h, target))
145 handlers[hand] = h
146 #now all handlers are loaded, fixup inter-handler references...
147 for h, t in fixups:
148 h.setTarget(handlers[t])
149 return handlers
150
151
152def _install_loggers(cp, handlers):
153 """Create and install loggers"""
154
155 # configure the root first
156 llist = cp.get("loggers", "keys")
157 llist = string.split(llist, ",")
158 llist.remove("root")
159 sectname = "logger_root"
160 root = logging.root
161 log = root
162 opts = cp.options(sectname)
163 if "level" in opts:
164 level = cp.get(sectname, "level")
165 log.setLevel(logging._levelNames[level])
166 for h in root.handlers[:]:
167 root.removeHandler(h)
168 hlist = cp.get(sectname, "handlers")
169 if len(hlist):
170 hlist = string.split(hlist, ",")
171 for hand in hlist:
172 log.addHandler(handlers[hand])
173
174 #and now the others...
175 #we don't want to lose the existing loggers,
176 #since other threads may have pointers to them.
177 #existing is set to contain all existing loggers,
178 #and as we go through the new configuration we
179 #remove any which are configured. At the end,
180 #what's left in existing is the set of loggers
181 #which were in the previous configuration but
182 #which are not in the new configuration.
183 existing = root.manager.loggerDict.keys()
184 #now set up the new ones...
185 for log in llist:
186 sectname = "logger_%s" % log
187 qn = cp.get(sectname, "qualname")
188 opts = cp.options(sectname)
189 if "propagate" in opts:
190 propagate = cp.getint(sectname, "propagate")
191 else:
192 propagate = 1
193 logger = logging.getLogger(qn)
194 if qn in existing:
195 existing.remove(qn)
196 if "level" in opts:
197 level = cp.get(sectname, "level")
198 logger.setLevel(logging._levelNames[level])
199 for h in logger.handlers[:]:
200 logger.removeHandler(h)
201 logger.propagate = propagate
202 logger.disabled = 0
203 hlist = cp.get(sectname, "handlers")
204 if len(hlist):
205 hlist = string.split(hlist, ",")
206 for hand in hlist:
207 logger.addHandler(handlers[hand])
208
209 #Disable any old loggers. There's no point deleting
210 #them as other threads may continue to hold references
211 #and by disabling them, you stop them doing any logging.
212 for log in existing:
213 root.manager.loggerDict[log].disabled = 1
214
215
Guido van Rossum57102f82002-11-13 16:15:58 +0000216def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
217 """
218 Start up a socket server on the specified port, and listen for new
219 configurations.
220
221 These will be sent as a file suitable for processing by fileConfig().
222 Returns a Thread object on which you can call start() to start the server,
223 and which you can join() when appropriate. To stop the server, call
224 stopListening().
225 """
226 if not thread:
227 raise NotImplementedError, "listen() needs threading to work"
228
229 class ConfigStreamHandler(StreamRequestHandler):
230 """
231 Handler for a logging configuration request.
232
233 It expects a completely new logging configuration and uses fileConfig
234 to install it.
235 """
236 def handle(self):
237 """
238 Handle a request.
239
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000240 Each request is expected to be a 4-byte length, packed using
241 struct.pack(">L", n), followed by the config file.
242 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000243 """
244 import tempfile
245 try:
246 conn = self.connection
247 chunk = conn.recv(4)
248 if len(chunk) == 4:
249 slen = struct.unpack(">L", chunk)[0]
250 chunk = self.connection.recv(slen)
251 while len(chunk) < slen:
252 chunk = chunk + conn.recv(slen - len(chunk))
253 #Apply new configuration. We'd like to be able to
254 #create a StringIO and pass that in, but unfortunately
255 #1.5.2 ConfigParser does not support reading file
256 #objects, only actual files. So we create a temporary
257 #file and remove it later.
258 file = tempfile.mktemp(".ini")
259 f = open(file, "w")
260 f.write(chunk)
261 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000262 try:
263 fileConfig(file)
264 except (KeyboardInterrupt, SystemExit):
265 raise
266 except:
267 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000268 os.remove(file)
269 except socket.error, e:
270 if type(e.args) != types.TupleType:
271 raise
272 else:
273 errcode = e.args[0]
274 if errcode != RESET_ERROR:
275 raise
276
277 class ConfigSocketReceiver(ThreadingTCPServer):
278 """
279 A simple TCP socket-based logging config receiver.
280 """
281
282 allow_reuse_address = 1
283
284 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000285 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000286 ThreadingTCPServer.__init__(self, (host, port), handler)
287 logging._acquireLock()
288 self.abort = 0
289 logging._releaseLock()
290 self.timeout = 1
291
292 def serve_until_stopped(self):
293 import select
294 abort = 0
295 while not abort:
296 rd, wr, ex = select.select([self.socket.fileno()],
297 [], [],
298 self.timeout)
299 if rd:
300 self.handle_request()
301 logging._acquireLock()
302 abort = self.abort
303 logging._releaseLock()
304
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000305 def serve(rcvr, hdlr, port):
306 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000307 global _listener
308 logging._acquireLock()
309 _listener = server
310 logging._releaseLock()
311 server.serve_until_stopped()
312
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000313 return threading.Thread(target=serve,
314 args=(ConfigSocketReceiver,
315 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000316
317def stopListening():
318 """
319 Stop the listening server which was created with a call to listen().
320 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000321 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000322 if _listener:
323 logging._acquireLock()
324 _listener.abort = 1
325 _listener = None
326 logging._releaseLock()