blob: a5c780fe5d9494bccd9e13c77695103f6305d097 [file] [log] [blame]
Vinay Sajip3f742842004-02-28 16:07:46 +00001# Copyright 2001-2004 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 Sajip612df8e2005-02-18 11:54:46 +000030import sys, logging, logging.handlers, string, socket, struct, os
31
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)
75 #first, do the formatters...
76 flist = cp.get("formatters", "keys")
77 if len(flist):
78 flist = string.split(flist, ",")
79 formatters = {}
80 for form in flist:
81 sectname = "formatter_%s" % form
82 opts = cp.options(sectname)
83 if "format" in opts:
84 fs = cp.get(sectname, "format", 1)
85 else:
86 fs = None
87 if "datefmt" in opts:
88 dfs = cp.get(sectname, "datefmt", 1)
89 else:
90 dfs = None
91 f = logging.Formatter(fs, dfs)
92 formatters[form] = f
93 #next, do the handlers...
94 #critical section...
95 logging._acquireLock()
96 try:
97 try:
98 #first, lose the existing handlers...
99 logging._handlers.clear()
100 #now set up the new ones...
101 hlist = cp.get("handlers", "keys")
102 if len(hlist):
103 hlist = string.split(hlist, ",")
104 handlers = {}
105 fixups = [] #for inter-handler references
106 for hand in hlist:
Vinay Sajip02dd9942004-09-24 11:45:13 +0000107 try:
108 sectname = "handler_%s" % hand
109 klass = cp.get(sectname, "class")
110 opts = cp.options(sectname)
111 if "formatter" in opts:
112 fmt = cp.get(sectname, "formatter")
Guido van Rossum57102f82002-11-13 16:15:58 +0000113 else:
Vinay Sajip02dd9942004-09-24 11:45:13 +0000114 fmt = ""
115 klass = eval(klass, vars(logging))
116 args = cp.get(sectname, "args")
117 args = eval(args, vars(logging))
118 h = apply(klass, args)
119 if "level" in opts:
120 level = cp.get(sectname, "level")
121 h.setLevel(logging._levelNames[level])
122 if len(fmt):
123 h.setFormatter(formatters[fmt])
124 #temporary hack for FileHandler and MemoryHandler.
125 if klass == logging.handlers.MemoryHandler:
126 if "target" in opts:
127 target = cp.get(sectname,"target")
128 else:
129 target = ""
130 if len(target): #the target handler may not be loaded yet, so keep for later...
131 fixups.append((h, target))
132 handlers[hand] = h
133 except: #if an error occurs when instantiating a handler, too bad
134 pass #this could happen e.g. because of lack of privileges
Guido van Rossum57102f82002-11-13 16:15:58 +0000135 #now all handlers are loaded, fixup inter-handler references...
136 for fixup in fixups:
137 h = fixup[0]
138 t = fixup[1]
139 h.setTarget(handlers[t])
140 #at last, the loggers...first the root...
141 llist = cp.get("loggers", "keys")
142 llist = string.split(llist, ",")
143 llist.remove("root")
144 sectname = "logger_root"
145 root = logging.root
146 log = root
147 opts = cp.options(sectname)
148 if "level" in opts:
149 level = cp.get(sectname, "level")
150 log.setLevel(logging._levelNames[level])
Guido van Rossum24475892002-12-20 01:54:21 +0000151 for h in root.handlers[:]:
Guido van Rossum57102f82002-11-13 16:15:58 +0000152 root.removeHandler(h)
153 hlist = cp.get(sectname, "handlers")
154 if len(hlist):
155 hlist = string.split(hlist, ",")
156 for hand in hlist:
157 log.addHandler(handlers[hand])
158 #and now the others...
159 #we don't want to lose the existing loggers,
160 #since other threads may have pointers to them.
161 #existing is set to contain all existing loggers,
162 #and as we go through the new configuration we
163 #remove any which are configured. At the end,
164 #what's left in existing is the set of loggers
165 #which were in the previous configuration but
166 #which are not in the new configuration.
167 existing = root.manager.loggerDict.keys()
168 #now set up the new ones...
169 for log in llist:
170 sectname = "logger_%s" % log
171 qn = cp.get(sectname, "qualname")
172 opts = cp.options(sectname)
173 if "propagate" in opts:
174 propagate = cp.getint(sectname, "propagate")
175 else:
176 propagate = 1
177 logger = logging.getLogger(qn)
178 if qn in existing:
179 existing.remove(qn)
180 if "level" in opts:
181 level = cp.get(sectname, "level")
182 logger.setLevel(logging._levelNames[level])
Guido van Rossum24475892002-12-20 01:54:21 +0000183 for h in logger.handlers[:]:
Guido van Rossum57102f82002-11-13 16:15:58 +0000184 logger.removeHandler(h)
185 logger.propagate = propagate
186 logger.disabled = 0
187 hlist = cp.get(sectname, "handlers")
188 if len(hlist):
189 hlist = string.split(hlist, ",")
190 for hand in hlist:
191 logger.addHandler(handlers[hand])
192 #Disable any old loggers. There's no point deleting
193 #them as other threads may continue to hold references
194 #and by disabling them, you stop them doing any logging.
195 for log in existing:
196 root.manager.loggerDict[log].disabled = 1
197 except:
198 import traceback
199 ei = sys.exc_info()
200 traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
201 del ei
202 finally:
203 logging._releaseLock()
204
205def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
206 """
207 Start up a socket server on the specified port, and listen for new
208 configurations.
209
210 These will be sent as a file suitable for processing by fileConfig().
211 Returns a Thread object on which you can call start() to start the server,
212 and which you can join() when appropriate. To stop the server, call
213 stopListening().
214 """
215 if not thread:
216 raise NotImplementedError, "listen() needs threading to work"
217
218 class ConfigStreamHandler(StreamRequestHandler):
219 """
220 Handler for a logging configuration request.
221
222 It expects a completely new logging configuration and uses fileConfig
223 to install it.
224 """
225 def handle(self):
226 """
227 Handle a request.
228
229 Each request is expected to be a 4-byte length,
230 followed by the config file. Uses fileConfig() to do the
231 grunt work.
232 """
233 import tempfile
234 try:
235 conn = self.connection
236 chunk = conn.recv(4)
237 if len(chunk) == 4:
238 slen = struct.unpack(">L", chunk)[0]
239 chunk = self.connection.recv(slen)
240 while len(chunk) < slen:
241 chunk = chunk + conn.recv(slen - len(chunk))
242 #Apply new configuration. We'd like to be able to
243 #create a StringIO and pass that in, but unfortunately
244 #1.5.2 ConfigParser does not support reading file
245 #objects, only actual files. So we create a temporary
246 #file and remove it later.
247 file = tempfile.mktemp(".ini")
248 f = open(file, "w")
249 f.write(chunk)
250 f.close()
251 fileConfig(file)
252 os.remove(file)
253 except socket.error, e:
254 if type(e.args) != types.TupleType:
255 raise
256 else:
257 errcode = e.args[0]
258 if errcode != RESET_ERROR:
259 raise
260
261 class ConfigSocketReceiver(ThreadingTCPServer):
262 """
263 A simple TCP socket-based logging config receiver.
264 """
265
266 allow_reuse_address = 1
267
268 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000269 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000270 ThreadingTCPServer.__init__(self, (host, port), handler)
271 logging._acquireLock()
272 self.abort = 0
273 logging._releaseLock()
274 self.timeout = 1
275
276 def serve_until_stopped(self):
277 import select
278 abort = 0
279 while not abort:
280 rd, wr, ex = select.select([self.socket.fileno()],
281 [], [],
282 self.timeout)
283 if rd:
284 self.handle_request()
285 logging._acquireLock()
286 abort = self.abort
287 logging._releaseLock()
288
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000289 def serve(rcvr, hdlr, port):
290 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000291 global _listener
292 logging._acquireLock()
293 _listener = server
294 logging._releaseLock()
295 server.serve_until_stopped()
296
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000297 return threading.Thread(target=serve,
298 args=(ConfigSocketReceiver,
299 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000300
301def stopListening():
302 """
303 Stop the listening server which was created with a call to listen().
304 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000305 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000306 if _listener:
307 logging._acquireLock()
308 _listener.abort = 1
309 _listener = None
310 logging._releaseLock()