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