blob: 645a7ba30fa1034ca5a6b2878c42766d3b3223af [file] [log] [blame]
Christian Heimes96f31632007-11-12 01:32:03 +00001# Copyright 2001-2007 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
Christian Heimes96f31632007-11-12 01:32:03 +000022Copyright (C) 2001-2007 Vinay Sajip. All Rights Reserved.
Guido van Rossum57102f82002-11-13 16:15:58 +000023
24To use, simply 'import logging' and log away!
25"""
26
Guido van Rossum13257902007-06-07 23:15:56 +000027import sys, logging, logging.handlers, socket, struct, os, traceback
Vinay Sajip612df8e2005-02-18 11:54:46 +000028
29try:
Georg Brandl2067bfd2008-05-25 13:05:15 +000030 import _thread as thread
Vinay Sajip612df8e2005-02-18 11:54:46 +000031 import threading
32except ImportError:
33 thread = None
Guido van Rossum57102f82002-11-13 16:15:58 +000034
Alexandre Vassalottice261952008-05-12 02:31:37 +000035from socketserver import ThreadingTCPServer, StreamRequestHandler
Guido van Rossum57102f82002-11-13 16:15:58 +000036
37
38DEFAULT_LOGGING_CONFIG_PORT = 9030
39
Vinay Sajip326441e2004-02-20 13:16:36 +000040if sys.platform == "win32":
41 RESET_ERROR = 10054 #WSAECONNRESET
42else:
43 RESET_ERROR = 104 #ECONNRESET
44
Guido van Rossum57102f82002-11-13 16:15:58 +000045#
46# The following code implements a socket listener for on-the-fly
47# reconfiguration of logging.
48#
49# _listener holds the server object doing the listening
50_listener = None
51
Benjamin Petersonfea6a942008-07-02 16:11:42 +000052def fileConfig(fname, defaults=None, disable_existing_loggers=1):
Guido van Rossum57102f82002-11-13 16:15:58 +000053 """
54 Read the logging configuration from a ConfigParser-format file.
55
56 This can be called several times from an application, allowing an end user
57 the ability to select from various pre-canned configurations (if the
58 developer provides a mechanism to present the choices and load the chosen
59 configuration).
60 In versions of ConfigParser which have the readfp method [typically
61 shipped in 2.x versions of Python], you can pass in a file-like object
62 rather than a filename, in which case the file-like object will be read
63 using readfp.
64 """
Alexandre Vassalotti1d1eaa42008-05-14 22:59:42 +000065 import configparser
Guido van Rossum57102f82002-11-13 16:15:58 +000066
Alexandre Vassalotti1d1eaa42008-05-14 22:59:42 +000067 cp = configparser.ConfigParser(defaults)
Guido van Rossum57102f82002-11-13 16:15:58 +000068 if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
69 cp.readfp(fname)
70 else:
71 cp.read(fname)
Vinay Sajip989b69a2006-01-16 21:28:37 +000072
73 formatters = _create_formatters(cp)
74
75 # critical section
Guido van Rossum57102f82002-11-13 16:15:58 +000076 logging._acquireLock()
77 try:
Vinay Sajip989b69a2006-01-16 21:28:37 +000078 logging._handlers.clear()
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000079 del logging._handlerList[:]
Vinay Sajip989b69a2006-01-16 21:28:37 +000080 # Handlers add themselves to logging._handlers
81 handlers = _install_handlers(cp, formatters)
Benjamin Petersonfea6a942008-07-02 16:11:42 +000082 _install_loggers(cp, handlers, disable_existing_loggers)
Guido van Rossum57102f82002-11-13 16:15:58 +000083 finally:
84 logging._releaseLock()
85
Vinay Sajip989b69a2006-01-16 21:28:37 +000086
Vinay Sajip7a7160b2006-01-20 18:28:03 +000087def _resolve(name):
88 """Resolve a dotted name to a global object."""
Neal Norwitz9d72bb42007-04-17 08:48:32 +000089 name = name.split('.')
Vinay Sajip7a7160b2006-01-20 18:28:03 +000090 used = name.pop(0)
91 found = __import__(used)
92 for n in name:
93 used = used + '.' + n
94 try:
95 found = getattr(found, n)
96 except AttributeError:
97 __import__(used)
98 found = getattr(found, n)
99 return found
100
101
Vinay Sajip989b69a2006-01-16 21:28:37 +0000102def _create_formatters(cp):
103 """Create and return formatters"""
104 flist = cp.get("formatters", "keys")
105 if not len(flist):
106 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000107 flist = flist.split(",")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000108 formatters = {}
109 for form in flist:
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000110 sectname = "formatter_%s" % form.strip()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000111 opts = cp.options(sectname)
112 if "format" in opts:
113 fs = cp.get(sectname, "format", 1)
114 else:
115 fs = None
116 if "datefmt" in opts:
117 dfs = cp.get(sectname, "datefmt", 1)
118 else:
119 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000120 c = logging.Formatter
121 if "class" in opts:
122 class_name = cp.get(sectname, "class")
123 if class_name:
124 c = _resolve(class_name)
125 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000126 formatters[form] = f
127 return formatters
128
129
130def _install_handlers(cp, formatters):
131 """Install and return handlers"""
132 hlist = cp.get("handlers", "keys")
133 if not len(hlist):
134 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000135 hlist = hlist.split(",")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000136 handlers = {}
137 fixups = [] #for inter-handler references
138 for hand in hlist:
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000139 sectname = "handler_%s" % hand.strip()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000140 klass = cp.get(sectname, "class")
141 opts = cp.options(sectname)
142 if "formatter" in opts:
143 fmt = cp.get(sectname, "formatter")
144 else:
145 fmt = ""
146 klass = eval(klass, vars(logging))
147 args = cp.get(sectname, "args")
148 args = eval(args, vars(logging))
Neal Norwitzd9108552006-03-17 08:00:19 +0000149 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000150 if "level" in opts:
151 level = cp.get(sectname, "level")
152 h.setLevel(logging._levelNames[level])
153 if len(fmt):
154 h.setFormatter(formatters[fmt])
155 #temporary hack for FileHandler and MemoryHandler.
156 if klass == logging.handlers.MemoryHandler:
157 if "target" in opts:
158 target = cp.get(sectname,"target")
159 else:
160 target = ""
161 if len(target): #the target handler may not be loaded yet, so keep for later...
162 fixups.append((h, target))
163 handlers[hand] = h
164 #now all handlers are loaded, fixup inter-handler references...
165 for h, t in fixups:
166 h.setTarget(handlers[t])
167 return handlers
168
169
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000170def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000171 """Create and install loggers"""
172
173 # configure the root first
174 llist = cp.get("loggers", "keys")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000175 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000176 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000177 llist.remove("root")
178 sectname = "logger_root"
179 root = logging.root
180 log = root
181 opts = cp.options(sectname)
182 if "level" in opts:
183 level = cp.get(sectname, "level")
184 log.setLevel(logging._levelNames[level])
185 for h in root.handlers[:]:
186 root.removeHandler(h)
187 hlist = cp.get(sectname, "handlers")
188 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000189 hlist = hlist.split(",")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000190 for hand in hlist:
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000191 log.addHandler(handlers[hand.strip()])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000192
193 #and now the others...
194 #we don't want to lose the existing loggers,
195 #since other threads may have pointers to them.
196 #existing is set to contain all existing loggers,
197 #and as we go through the new configuration we
198 #remove any which are configured. At the end,
199 #what's left in existing is the set of loggers
200 #which were in the previous configuration but
201 #which are not in the new configuration.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000202 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +0000203 #The list needs to be sorted so that we can
204 #avoid disabling child loggers of explicitly
205 #named loggers. With a sorted list it is easier
206 #to find the child loggers.
207 existing.sort()
208 #We'll keep the list of existing loggers
209 #which are children of named loggers here...
210 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000211 #now set up the new ones...
212 for log in llist:
213 sectname = "logger_%s" % log
214 qn = cp.get(sectname, "qualname")
215 opts = cp.options(sectname)
216 if "propagate" in opts:
217 propagate = cp.getint(sectname, "propagate")
218 else:
219 propagate = 1
220 logger = logging.getLogger(qn)
221 if qn in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000222 i = existing.index(qn)
223 prefixed = qn + "."
224 pflen = len(prefixed)
225 num_existing = len(existing)
226 i = i + 1 # look at the entry after qn
227 while (i < num_existing) and (existing[i][:pflen] == prefixed):
228 child_loggers.append(existing[i])
229 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000230 existing.remove(qn)
231 if "level" in opts:
232 level = cp.get(sectname, "level")
233 logger.setLevel(logging._levelNames[level])
234 for h in logger.handlers[:]:
235 logger.removeHandler(h)
236 logger.propagate = propagate
237 logger.disabled = 0
238 hlist = cp.get(sectname, "handlers")
239 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000240 hlist = hlist.split(",")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000241 for hand in hlist:
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000242 logger.addHandler(handlers[hand.strip()])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000243
244 #Disable any old loggers. There's no point deleting
245 #them as other threads may continue to hold references
246 #and by disabling them, you stop them doing any logging.
Christian Heimes96f31632007-11-12 01:32:03 +0000247 #However, don't disable children of named loggers, as that's
248 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000249 for log in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000250 logger = root.manager.loggerDict[log]
251 if log in child_loggers:
252 logger.level = logging.NOTSET
253 logger.handlers = []
254 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000255 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000256 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000257
258
Guido van Rossum57102f82002-11-13 16:15:58 +0000259def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
260 """
261 Start up a socket server on the specified port, and listen for new
262 configurations.
263
264 These will be sent as a file suitable for processing by fileConfig().
265 Returns a Thread object on which you can call start() to start the server,
266 and which you can join() when appropriate. To stop the server, call
267 stopListening().
268 """
269 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000270 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000271
272 class ConfigStreamHandler(StreamRequestHandler):
273 """
274 Handler for a logging configuration request.
275
276 It expects a completely new logging configuration and uses fileConfig
277 to install it.
278 """
279 def handle(self):
280 """
281 Handle a request.
282
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000283 Each request is expected to be a 4-byte length, packed using
284 struct.pack(">L", n), followed by the config file.
285 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000286 """
287 import tempfile
288 try:
289 conn = self.connection
290 chunk = conn.recv(4)
291 if len(chunk) == 4:
292 slen = struct.unpack(">L", chunk)[0]
293 chunk = self.connection.recv(slen)
294 while len(chunk) < slen:
295 chunk = chunk + conn.recv(slen - len(chunk))
296 #Apply new configuration. We'd like to be able to
297 #create a StringIO and pass that in, but unfortunately
298 #1.5.2 ConfigParser does not support reading file
299 #objects, only actual files. So we create a temporary
300 #file and remove it later.
301 file = tempfile.mktemp(".ini")
302 f = open(file, "w")
303 f.write(chunk)
304 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000305 try:
306 fileConfig(file)
307 except (KeyboardInterrupt, SystemExit):
308 raise
309 except:
310 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000311 os.remove(file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000312 except socket.error as e:
Guido van Rossum13257902007-06-07 23:15:56 +0000313 if not isinstancetype(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000314 raise
315 else:
316 errcode = e.args[0]
317 if errcode != RESET_ERROR:
318 raise
319
320 class ConfigSocketReceiver(ThreadingTCPServer):
321 """
322 A simple TCP socket-based logging config receiver.
323 """
324
325 allow_reuse_address = 1
326
327 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000328 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000329 ThreadingTCPServer.__init__(self, (host, port), handler)
330 logging._acquireLock()
331 self.abort = 0
332 logging._releaseLock()
333 self.timeout = 1
334
335 def serve_until_stopped(self):
336 import select
337 abort = 0
338 while not abort:
339 rd, wr, ex = select.select([self.socket.fileno()],
340 [], [],
341 self.timeout)
342 if rd:
343 self.handle_request()
344 logging._acquireLock()
345 abort = self.abort
346 logging._releaseLock()
347
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000348 def serve(rcvr, hdlr, port):
349 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000350 global _listener
351 logging._acquireLock()
352 _listener = server
353 logging._releaseLock()
354 server.serve_until_stopped()
355
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000356 return threading.Thread(target=serve,
357 args=(ConfigSocketReceiver,
358 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000359
360def stopListening():
361 """
362 Stop the listening server which was created with a call to listen().
363 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000364 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000365 if _listener:
366 logging._acquireLock()
367 _listener.abort = 1
368 _listener = None
369 logging._releaseLock()