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