blob: ea157db5bfc5b7bd08b916667438e5722bfbd8e6 [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)
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:
Guido van Rossum57102f82002-11-13 16:15:58 +0000198 ei = sys.exc_info()
199 traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
200 del ei
201 finally:
202 logging._releaseLock()
203
204def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
205 """
206 Start up a socket server on the specified port, and listen for new
207 configurations.
208
209 These will be sent as a file suitable for processing by fileConfig().
210 Returns a Thread object on which you can call start() to start the server,
211 and which you can join() when appropriate. To stop the server, call
212 stopListening().
213 """
214 if not thread:
215 raise NotImplementedError, "listen() needs threading to work"
216
217 class ConfigStreamHandler(StreamRequestHandler):
218 """
219 Handler for a logging configuration request.
220
221 It expects a completely new logging configuration and uses fileConfig
222 to install it.
223 """
224 def handle(self):
225 """
226 Handle a request.
227
228 Each request is expected to be a 4-byte length,
229 followed by the config file. Uses fileConfig() to do the
230 grunt work.
231 """
232 import tempfile
233 try:
234 conn = self.connection
235 chunk = conn.recv(4)
236 if len(chunk) == 4:
237 slen = struct.unpack(">L", chunk)[0]
238 chunk = self.connection.recv(slen)
239 while len(chunk) < slen:
240 chunk = chunk + conn.recv(slen - len(chunk))
241 #Apply new configuration. We'd like to be able to
242 #create a StringIO and pass that in, but unfortunately
243 #1.5.2 ConfigParser does not support reading file
244 #objects, only actual files. So we create a temporary
245 #file and remove it later.
246 file = tempfile.mktemp(".ini")
247 f = open(file, "w")
248 f.write(chunk)
249 f.close()
250 fileConfig(file)
251 os.remove(file)
252 except socket.error, e:
253 if type(e.args) != types.TupleType:
254 raise
255 else:
256 errcode = e.args[0]
257 if errcode != RESET_ERROR:
258 raise
259
260 class ConfigSocketReceiver(ThreadingTCPServer):
261 """
262 A simple TCP socket-based logging config receiver.
263 """
264
265 allow_reuse_address = 1
266
267 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000268 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000269 ThreadingTCPServer.__init__(self, (host, port), handler)
270 logging._acquireLock()
271 self.abort = 0
272 logging._releaseLock()
273 self.timeout = 1
274
275 def serve_until_stopped(self):
276 import select
277 abort = 0
278 while not abort:
279 rd, wr, ex = select.select([self.socket.fileno()],
280 [], [],
281 self.timeout)
282 if rd:
283 self.handle_request()
284 logging._acquireLock()
285 abort = self.abort
286 logging._releaseLock()
287
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000288 def serve(rcvr, hdlr, port):
289 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000290 global _listener
291 logging._acquireLock()
292 _listener = server
293 logging._releaseLock()
294 server.serve_until_stopped()
295
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000296 return threading.Thread(target=serve,
297 args=(ConfigSocketReceiver,
298 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000299
300def stopListening():
301 """
302 Stop the listening server which was created with a call to listen().
303 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000304 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000305 if _listener:
306 logging._acquireLock()
307 _listener.abort = 1
308 _listener = None
309 logging._releaseLock()