blob: c9ffab03e57564f5da1b8ec37df3f38c9aed9a63 [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:
101 sectname = "handler_%s" % hand
102 klass = cp.get(sectname, "class")
103 opts = cp.options(sectname)
104 if "formatter" in opts:
105 fmt = cp.get(sectname, "formatter")
106 else:
107 fmt = ""
108 klass = eval(klass, vars(logging))
109 args = cp.get(sectname, "args")
110 args = eval(args, vars(logging))
Guido van Rossum0df64422003-03-02 20:47:29 +0000111 h = apply(klass, args)
Guido van Rossum57102f82002-11-13 16:15:58 +0000112 if "level" in opts:
113 level = cp.get(sectname, "level")
114 h.setLevel(logging._levelNames[level])
115 if len(fmt):
116 h.setFormatter(formatters[fmt])
117 #temporary hack for FileHandler and MemoryHandler.
118 if klass == logging.handlers.MemoryHandler:
119 if "target" in opts:
120 target = cp.get(sectname,"target")
121 else:
122 target = ""
123 if len(target): #the target handler may not be loaded yet, so keep for later...
124 fixups.append((h, target))
125 handlers[hand] = h
126 #now all handlers are loaded, fixup inter-handler references...
127 for fixup in fixups:
128 h = fixup[0]
129 t = fixup[1]
130 h.setTarget(handlers[t])
131 #at last, the loggers...first the root...
132 llist = cp.get("loggers", "keys")
133 llist = string.split(llist, ",")
134 llist.remove("root")
135 sectname = "logger_root"
136 root = logging.root
137 log = root
138 opts = cp.options(sectname)
139 if "level" in opts:
140 level = cp.get(sectname, "level")
141 log.setLevel(logging._levelNames[level])
Guido van Rossum24475892002-12-20 01:54:21 +0000142 for h in root.handlers[:]:
Guido van Rossum57102f82002-11-13 16:15:58 +0000143 root.removeHandler(h)
144 hlist = cp.get(sectname, "handlers")
145 if len(hlist):
146 hlist = string.split(hlist, ",")
147 for hand in hlist:
148 log.addHandler(handlers[hand])
149 #and now the others...
150 #we don't want to lose the existing loggers,
151 #since other threads may have pointers to them.
152 #existing is set to contain all existing loggers,
153 #and as we go through the new configuration we
154 #remove any which are configured. At the end,
155 #what's left in existing is the set of loggers
156 #which were in the previous configuration but
157 #which are not in the new configuration.
158 existing = root.manager.loggerDict.keys()
159 #now set up the new ones...
160 for log in llist:
161 sectname = "logger_%s" % log
162 qn = cp.get(sectname, "qualname")
163 opts = cp.options(sectname)
164 if "propagate" in opts:
165 propagate = cp.getint(sectname, "propagate")
166 else:
167 propagate = 1
168 logger = logging.getLogger(qn)
169 if qn in existing:
170 existing.remove(qn)
171 if "level" in opts:
172 level = cp.get(sectname, "level")
173 logger.setLevel(logging._levelNames[level])
Guido van Rossum24475892002-12-20 01:54:21 +0000174 for h in logger.handlers[:]:
Guido van Rossum57102f82002-11-13 16:15:58 +0000175 logger.removeHandler(h)
176 logger.propagate = propagate
177 logger.disabled = 0
178 hlist = cp.get(sectname, "handlers")
179 if len(hlist):
180 hlist = string.split(hlist, ",")
181 for hand in hlist:
182 logger.addHandler(handlers[hand])
183 #Disable any old loggers. There's no point deleting
184 #them as other threads may continue to hold references
185 #and by disabling them, you stop them doing any logging.
186 for log in existing:
187 root.manager.loggerDict[log].disabled = 1
188 except:
189 import traceback
190 ei = sys.exc_info()
191 traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
192 del ei
193 finally:
194 logging._releaseLock()
195
196def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
197 """
198 Start up a socket server on the specified port, and listen for new
199 configurations.
200
201 These will be sent as a file suitable for processing by fileConfig().
202 Returns a Thread object on which you can call start() to start the server,
203 and which you can join() when appropriate. To stop the server, call
204 stopListening().
205 """
206 if not thread:
207 raise NotImplementedError, "listen() needs threading to work"
208
209 class ConfigStreamHandler(StreamRequestHandler):
210 """
211 Handler for a logging configuration request.
212
213 It expects a completely new logging configuration and uses fileConfig
214 to install it.
215 """
216 def handle(self):
217 """
218 Handle a request.
219
220 Each request is expected to be a 4-byte length,
221 followed by the config file. Uses fileConfig() to do the
222 grunt work.
223 """
224 import tempfile
225 try:
226 conn = self.connection
227 chunk = conn.recv(4)
228 if len(chunk) == 4:
229 slen = struct.unpack(">L", chunk)[0]
230 chunk = self.connection.recv(slen)
231 while len(chunk) < slen:
232 chunk = chunk + conn.recv(slen - len(chunk))
233 #Apply new configuration. We'd like to be able to
234 #create a StringIO and pass that in, but unfortunately
235 #1.5.2 ConfigParser does not support reading file
236 #objects, only actual files. So we create a temporary
237 #file and remove it later.
238 file = tempfile.mktemp(".ini")
239 f = open(file, "w")
240 f.write(chunk)
241 f.close()
242 fileConfig(file)
243 os.remove(file)
244 except socket.error, e:
245 if type(e.args) != types.TupleType:
246 raise
247 else:
248 errcode = e.args[0]
249 if errcode != RESET_ERROR:
250 raise
251
252 class ConfigSocketReceiver(ThreadingTCPServer):
253 """
254 A simple TCP socket-based logging config receiver.
255 """
256
257 allow_reuse_address = 1
258
259 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000260 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000261 ThreadingTCPServer.__init__(self, (host, port), handler)
262 logging._acquireLock()
263 self.abort = 0
264 logging._releaseLock()
265 self.timeout = 1
266
267 def serve_until_stopped(self):
268 import select
269 abort = 0
270 while not abort:
271 rd, wr, ex = select.select([self.socket.fileno()],
272 [], [],
273 self.timeout)
274 if rd:
275 self.handle_request()
276 logging._acquireLock()
277 abort = self.abort
278 logging._releaseLock()
279
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000280 def serve(rcvr, hdlr, port):
281 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000282 global _listener
283 logging._acquireLock()
284 _listener = server
285 logging._releaseLock()
286 server.serve_until_stopped()
287
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000288 return threading.Thread(target=serve,
289 args=(ConfigSocketReceiver,
290 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000291
292def stopListening():
293 """
294 Stop the listening server which was created with a call to listen().
295 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000296 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000297 if _listener:
298 logging._acquireLock()
299 _listener.abort = 1
300 _listener = None
301 logging._releaseLock()