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