Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | # Copyright 2001-2005 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. | |
16 | ||
17 | """ | |
18 | Configuration functions for the logging package for Python. The core package | |
19 | is based on PEP 282 and comments thereto in comp.lang.python, and influenced | |
20 | by Apache's log4j system. | |
21 | ||
22 | Should work under Python versions >= 1.5.2, except that source line | |
23 | information is not available unless 'sys._getframe()' is. | |
24 | ||
25 | Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved. | |
26 | ||
27 | To use, simply 'import logging' and log away! | |
28 | """ | |
29 | ||
30 | import sys, logging, logging.handlers, string, socket, struct, os, traceback | |
31 | ||
32 | try: | |
33 | import thread | |
34 | import threading | |
35 | except ImportError: | |
36 | thread = None | |
37 | ||
38 | from SocketServer import ThreadingTCPServer, StreamRequestHandler | |
39 | ||
40 | ||
41 | DEFAULT_LOGGING_CONFIG_PORT = 9030 | |
42 | ||
43 | if sys.platform == "win32": | |
44 | RESET_ERROR = 10054 #WSAECONNRESET | |
45 | else: | |
46 | RESET_ERROR = 104 #ECONNRESET | |
47 | ||
48 | # | |
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 | ||
55 | def fileConfig(fname, defaults=None): | |
56 | """ | |
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 | ||
70 | cp = ConfigParser.ConfigParser(defaults) | |
71 | 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: | |
107 | 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") | |
113 | else: | |
114 | 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 | |
135 | #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]) | |
151 | for h in root.handlers[:]: | |
152 | 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]) | |
183 | for h in logger.handlers[:]: | |
184 | 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: | |
198 | 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 | ||
204 | def 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, packed using | |
229 | struct.pack(">L", n), followed by the config file. | |
230 | Uses fileConfig() to do the 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, | |
268 | handler=None): | |
269 | 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 | ||
288 | def serve(rcvr, hdlr, port): | |
289 | server = rcvr(port=port, handler=hdlr) | |
290 | global _listener | |
291 | logging._acquireLock() | |
292 | _listener = server | |
293 | logging._releaseLock() | |
294 | server.serve_until_stopped() | |
295 | ||
296 | return threading.Thread(target=serve, | |
297 | args=(ConfigSocketReceiver, | |
298 | ConfigStreamHandler, port)) | |
299 | ||
300 | def stopListening(): | |
301 | """ | |
302 | Stop the listening server which was created with a call to listen(). | |
303 | """ | |
304 | global _listener | |
305 | if _listener: | |
306 | logging._acquireLock() | |
307 | _listener.abort = 1 | |
308 | _listener = None | |
309 | logging._releaseLock() |