blob: 92f2af0abb81954c6b9a2f15f3ad5c4dc8169c6f [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
30import sys, logging, logging.handlers, string, thread, threading, socket, struct, os
31
32from SocketServer import ThreadingTCPServer, StreamRequestHandler
33
34
35DEFAULT_LOGGING_CONFIG_PORT = 9030
36
Vinay Sajip326441e2004-02-20 13:16:36 +000037if sys.platform == "win32":
38 RESET_ERROR = 10054 #WSAECONNRESET
39else:
40 RESET_ERROR = 104 #ECONNRESET
41
Guido van Rossum57102f82002-11-13 16:15:58 +000042#
43# The following code implements a socket listener for on-the-fly
44# reconfiguration of logging.
45#
46# _listener holds the server object doing the listening
47_listener = None
48
Guido van Rossum31657862002-11-14 12:52:17 +000049def fileConfig(fname, defaults=None):
Guido van Rossum57102f82002-11-13 16:15:58 +000050 """
51 Read the logging configuration from a ConfigParser-format file.
52
53 This can be called several times from an application, allowing an end user
54 the ability to select from various pre-canned configurations (if the
55 developer provides a mechanism to present the choices and load the chosen
56 configuration).
57 In versions of ConfigParser which have the readfp method [typically
58 shipped in 2.x versions of Python], you can pass in a file-like object
59 rather than a filename, in which case the file-like object will be read
60 using readfp.
61 """
62 import ConfigParser
63
Guido van Rossum31657862002-11-14 12:52:17 +000064 cp = ConfigParser.ConfigParser(defaults)
Guido van Rossum57102f82002-11-13 16:15:58 +000065 if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
66 cp.readfp(fname)
67 else:
68 cp.read(fname)
69 #first, do the formatters...
70 flist = cp.get("formatters", "keys")
71 if len(flist):
72 flist = string.split(flist, ",")
73 formatters = {}
74 for form in flist:
75 sectname = "formatter_%s" % form
76 opts = cp.options(sectname)
77 if "format" in opts:
78 fs = cp.get(sectname, "format", 1)
79 else:
80 fs = None
81 if "datefmt" in opts:
82 dfs = cp.get(sectname, "datefmt", 1)
83 else:
84 dfs = None
85 f = logging.Formatter(fs, dfs)
86 formatters[form] = f
87 #next, do the handlers...
88 #critical section...
89 logging._acquireLock()
90 try:
91 try:
92 #first, lose the existing handlers...
93 logging._handlers.clear()
94 #now set up the new ones...
95 hlist = cp.get("handlers", "keys")
96 if len(hlist):
97 hlist = string.split(hlist, ",")
98 handlers = {}
99 fixups = [] #for inter-handler references
100 for hand in hlist:
Vinay Sajip02dd9942004-09-24 11:45:13 +0000101 try:
102 sectname = "handler_%s" % hand
103 klass = cp.get(sectname, "class")
104 opts = cp.options(sectname)
105 if "formatter" in opts:
106 fmt = cp.get(sectname, "formatter")
Guido van Rossum57102f82002-11-13 16:15:58 +0000107 else:
Vinay Sajip02dd9942004-09-24 11:45:13 +0000108 fmt = ""
109 klass = eval(klass, vars(logging))
110 args = cp.get(sectname, "args")
111 args = eval(args, vars(logging))
112 h = apply(klass, args)
113 if "level" in opts:
114 level = cp.get(sectname, "level")
115 h.setLevel(logging._levelNames[level])
116 if len(fmt):
117 h.setFormatter(formatters[fmt])
118 #temporary hack for FileHandler and MemoryHandler.
119 if klass == logging.handlers.MemoryHandler:
120 if "target" in opts:
121 target = cp.get(sectname,"target")
122 else:
123 target = ""
124 if len(target): #the target handler may not be loaded yet, so keep for later...
125 fixups.append((h, target))
126 handlers[hand] = h
127 except: #if an error occurs when instantiating a handler, too bad
128 pass #this could happen e.g. because of lack of privileges
Guido van Rossum57102f82002-11-13 16:15:58 +0000129 #now all handlers are loaded, fixup inter-handler references...
130 for fixup in fixups:
131 h = fixup[0]
132 t = fixup[1]
133 h.setTarget(handlers[t])
134 #at last, the loggers...first the root...
135 llist = cp.get("loggers", "keys")
136 llist = string.split(llist, ",")
137 llist.remove("root")
138 sectname = "logger_root"
139 root = logging.root
140 log = root
141 opts = cp.options(sectname)
142 if "level" in opts:
143 level = cp.get(sectname, "level")
144 log.setLevel(logging._levelNames[level])
Guido van Rossum24475892002-12-20 01:54:21 +0000145 for h in root.handlers[:]:
Guido van Rossum57102f82002-11-13 16:15:58 +0000146 root.removeHandler(h)
147 hlist = cp.get(sectname, "handlers")
148 if len(hlist):
149 hlist = string.split(hlist, ",")
150 for hand in hlist:
151 log.addHandler(handlers[hand])
152 #and now the others...
153 #we don't want to lose the existing loggers,
154 #since other threads may have pointers to them.
155 #existing is set to contain all existing loggers,
156 #and as we go through the new configuration we
157 #remove any which are configured. At the end,
158 #what's left in existing is the set of loggers
159 #which were in the previous configuration but
160 #which are not in the new configuration.
161 existing = root.manager.loggerDict.keys()
162 #now set up the new ones...
163 for log in llist:
164 sectname = "logger_%s" % log
165 qn = cp.get(sectname, "qualname")
166 opts = cp.options(sectname)
167 if "propagate" in opts:
168 propagate = cp.getint(sectname, "propagate")
169 else:
170 propagate = 1
171 logger = logging.getLogger(qn)
172 if qn in existing:
173 existing.remove(qn)
174 if "level" in opts:
175 level = cp.get(sectname, "level")
176 logger.setLevel(logging._levelNames[level])
Guido van Rossum24475892002-12-20 01:54:21 +0000177 for h in logger.handlers[:]:
Guido van Rossum57102f82002-11-13 16:15:58 +0000178 logger.removeHandler(h)
179 logger.propagate = propagate
180 logger.disabled = 0
181 hlist = cp.get(sectname, "handlers")
182 if len(hlist):
183 hlist = string.split(hlist, ",")
184 for hand in hlist:
185 logger.addHandler(handlers[hand])
186 #Disable any old loggers. There's no point deleting
187 #them as other threads may continue to hold references
188 #and by disabling them, you stop them doing any logging.
189 for log in existing:
190 root.manager.loggerDict[log].disabled = 1
191 except:
192 import traceback
193 ei = sys.exc_info()
194 traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
195 del ei
196 finally:
197 logging._releaseLock()
198
199def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
200 """
201 Start up a socket server on the specified port, and listen for new
202 configurations.
203
204 These will be sent as a file suitable for processing by fileConfig().
205 Returns a Thread object on which you can call start() to start the server,
206 and which you can join() when appropriate. To stop the server, call
207 stopListening().
208 """
209 if not thread:
210 raise NotImplementedError, "listen() needs threading to work"
211
212 class ConfigStreamHandler(StreamRequestHandler):
213 """
214 Handler for a logging configuration request.
215
216 It expects a completely new logging configuration and uses fileConfig
217 to install it.
218 """
219 def handle(self):
220 """
221 Handle a request.
222
223 Each request is expected to be a 4-byte length,
224 followed by the config file. Uses fileConfig() to do the
225 grunt work.
226 """
227 import tempfile
228 try:
229 conn = self.connection
230 chunk = conn.recv(4)
231 if len(chunk) == 4:
232 slen = struct.unpack(">L", chunk)[0]
233 chunk = self.connection.recv(slen)
234 while len(chunk) < slen:
235 chunk = chunk + conn.recv(slen - len(chunk))
236 #Apply new configuration. We'd like to be able to
237 #create a StringIO and pass that in, but unfortunately
238 #1.5.2 ConfigParser does not support reading file
239 #objects, only actual files. So we create a temporary
240 #file and remove it later.
241 file = tempfile.mktemp(".ini")
242 f = open(file, "w")
243 f.write(chunk)
244 f.close()
245 fileConfig(file)
246 os.remove(file)
247 except socket.error, e:
248 if type(e.args) != types.TupleType:
249 raise
250 else:
251 errcode = e.args[0]
252 if errcode != RESET_ERROR:
253 raise
254
255 class ConfigSocketReceiver(ThreadingTCPServer):
256 """
257 A simple TCP socket-based logging config receiver.
258 """
259
260 allow_reuse_address = 1
261
262 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000263 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000264 ThreadingTCPServer.__init__(self, (host, port), handler)
265 logging._acquireLock()
266 self.abort = 0
267 logging._releaseLock()
268 self.timeout = 1
269
270 def serve_until_stopped(self):
271 import select
272 abort = 0
273 while not abort:
274 rd, wr, ex = select.select([self.socket.fileno()],
275 [], [],
276 self.timeout)
277 if rd:
278 self.handle_request()
279 logging._acquireLock()
280 abort = self.abort
281 logging._releaseLock()
282
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000283 def serve(rcvr, hdlr, port):
284 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000285 global _listener
286 logging._acquireLock()
287 _listener = server
288 logging._releaseLock()
289 server.serve_until_stopped()
290
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000291 return threading.Thread(target=serve,
292 args=(ConfigSocketReceiver,
293 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000294
295def stopListening():
296 """
297 Stop the listening server which was created with a call to listen().
298 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000299 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000300 if _listener:
301 logging._acquireLock()
302 _listener.abort = 1
303 _listener = None
304 logging._releaseLock()