blob: 93d68a3c37ad6bb505eabaf9005a5c7290bc6b6b [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
Benjamin Petersonae5360b2008-09-08 23:05:23 +000022Copyright (C) 2001-2008 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
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000101def _strip_spaces(alist):
102 return map(lambda x: x.strip(), alist)
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000103
Vinay Sajip989b69a2006-01-16 21:28:37 +0000104def _create_formatters(cp):
105 """Create and return formatters"""
106 flist = cp.get("formatters", "keys")
107 if not len(flist):
108 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000109 flist = flist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000110 flist = _strip_spaces(flist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000111 formatters = {}
112 for form in flist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000113 sectname = "formatter_%s" % form
Vinay Sajip989b69a2006-01-16 21:28:37 +0000114 opts = cp.options(sectname)
115 if "format" in opts:
116 fs = cp.get(sectname, "format", 1)
117 else:
118 fs = None
119 if "datefmt" in opts:
120 dfs = cp.get(sectname, "datefmt", 1)
121 else:
122 dfs = None
Vinay Sajip7a7160b2006-01-20 18:28:03 +0000123 c = logging.Formatter
124 if "class" in opts:
125 class_name = cp.get(sectname, "class")
126 if class_name:
127 c = _resolve(class_name)
128 f = c(fs, dfs)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000129 formatters[form] = f
130 return formatters
131
132
133def _install_handlers(cp, formatters):
134 """Install and return handlers"""
135 hlist = cp.get("handlers", "keys")
136 if not len(hlist):
137 return {}
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000138 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000139 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000140 handlers = {}
141 fixups = [] #for inter-handler references
142 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000143 sectname = "handler_%s" % hand
Vinay Sajip989b69a2006-01-16 21:28:37 +0000144 klass = cp.get(sectname, "class")
145 opts = cp.options(sectname)
146 if "formatter" in opts:
147 fmt = cp.get(sectname, "formatter")
148 else:
149 fmt = ""
Georg Brandl3dbca812008-07-23 16:10:53 +0000150 try:
151 klass = eval(klass, vars(logging))
152 except (AttributeError, NameError):
153 klass = _resolve(klass)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000154 args = cp.get(sectname, "args")
155 args = eval(args, vars(logging))
Neal Norwitzd9108552006-03-17 08:00:19 +0000156 h = klass(*args)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000157 if "level" in opts:
158 level = cp.get(sectname, "level")
159 h.setLevel(logging._levelNames[level])
160 if len(fmt):
161 h.setFormatter(formatters[fmt])
Benjamin Peterson41181742008-07-02 20:22:54 +0000162 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000163 if "target" in opts:
164 target = cp.get(sectname,"target")
165 else:
166 target = ""
167 if len(target): #the target handler may not be loaded yet, so keep for later...
168 fixups.append((h, target))
169 handlers[hand] = h
170 #now all handlers are loaded, fixup inter-handler references...
171 for h, t in fixups:
172 h.setTarget(handlers[t])
173 return handlers
174
175
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000176def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000177 """Create and install loggers"""
178
179 # configure the root first
180 llist = cp.get("loggers", "keys")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000181 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000182 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000183 llist.remove("root")
184 sectname = "logger_root"
185 root = logging.root
186 log = root
187 opts = cp.options(sectname)
188 if "level" in opts:
189 level = cp.get(sectname, "level")
190 log.setLevel(logging._levelNames[level])
191 for h in root.handlers[:]:
192 root.removeHandler(h)
193 hlist = cp.get(sectname, "handlers")
194 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000195 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000196 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000197 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000198 log.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000199
200 #and now the others...
201 #we don't want to lose the existing loggers,
202 #since other threads may have pointers to them.
203 #existing is set to contain all existing loggers,
204 #and as we go through the new configuration we
205 #remove any which are configured. At the end,
206 #what's left in existing is the set of loggers
207 #which were in the previous configuration but
208 #which are not in the new configuration.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000209 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +0000210 #The list needs to be sorted so that we can
211 #avoid disabling child loggers of explicitly
212 #named loggers. With a sorted list it is easier
213 #to find the child loggers.
214 existing.sort()
215 #We'll keep the list of existing loggers
216 #which are children of named loggers here...
217 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000218 #now set up the new ones...
219 for log in llist:
220 sectname = "logger_%s" % log
221 qn = cp.get(sectname, "qualname")
222 opts = cp.options(sectname)
223 if "propagate" in opts:
224 propagate = cp.getint(sectname, "propagate")
225 else:
226 propagate = 1
227 logger = logging.getLogger(qn)
228 if qn in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000229 i = existing.index(qn)
230 prefixed = qn + "."
231 pflen = len(prefixed)
232 num_existing = len(existing)
233 i = i + 1 # look at the entry after qn
234 while (i < num_existing) and (existing[i][:pflen] == prefixed):
235 child_loggers.append(existing[i])
236 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000237 existing.remove(qn)
238 if "level" in opts:
239 level = cp.get(sectname, "level")
240 logger.setLevel(logging._levelNames[level])
241 for h in logger.handlers[:]:
242 logger.removeHandler(h)
243 logger.propagate = propagate
244 logger.disabled = 0
245 hlist = cp.get(sectname, "handlers")
246 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000247 hlist = hlist.split(",")
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000248 hlist = _strip_spaces(hlist)
Vinay Sajip989b69a2006-01-16 21:28:37 +0000249 for hand in hlist:
Benjamin Petersonae5360b2008-09-08 23:05:23 +0000250 logger.addHandler(handlers[hand])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000251
252 #Disable any old loggers. There's no point deleting
253 #them as other threads may continue to hold references
254 #and by disabling them, you stop them doing any logging.
Christian Heimes96f31632007-11-12 01:32:03 +0000255 #However, don't disable children of named loggers, as that's
256 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000257 for log in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000258 logger = root.manager.loggerDict[log]
259 if log in child_loggers:
260 logger.level = logging.NOTSET
261 logger.handlers = []
262 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000263 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000264 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000265
266
Guido van Rossum57102f82002-11-13 16:15:58 +0000267def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
268 """
269 Start up a socket server on the specified port, and listen for new
270 configurations.
271
272 These will be sent as a file suitable for processing by fileConfig().
273 Returns a Thread object on which you can call start() to start the server,
274 and which you can join() when appropriate. To stop the server, call
275 stopListening().
276 """
277 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000278 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000279
280 class ConfigStreamHandler(StreamRequestHandler):
281 """
282 Handler for a logging configuration request.
283
284 It expects a completely new logging configuration and uses fileConfig
285 to install it.
286 """
287 def handle(self):
288 """
289 Handle a request.
290
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000291 Each request is expected to be a 4-byte length, packed using
292 struct.pack(">L", n), followed by the config file.
293 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000294 """
295 import tempfile
296 try:
297 conn = self.connection
298 chunk = conn.recv(4)
299 if len(chunk) == 4:
300 slen = struct.unpack(">L", chunk)[0]
301 chunk = self.connection.recv(slen)
302 while len(chunk) < slen:
303 chunk = chunk + conn.recv(slen - len(chunk))
304 #Apply new configuration. We'd like to be able to
305 #create a StringIO and pass that in, but unfortunately
306 #1.5.2 ConfigParser does not support reading file
307 #objects, only actual files. So we create a temporary
308 #file and remove it later.
309 file = tempfile.mktemp(".ini")
310 f = open(file, "w")
311 f.write(chunk)
312 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000313 try:
314 fileConfig(file)
315 except (KeyboardInterrupt, SystemExit):
316 raise
317 except:
318 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000319 os.remove(file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000320 except socket.error as e:
Guido van Rossum13257902007-06-07 23:15:56 +0000321 if not isinstancetype(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000322 raise
323 else:
324 errcode = e.args[0]
325 if errcode != RESET_ERROR:
326 raise
327
328 class ConfigSocketReceiver(ThreadingTCPServer):
329 """
330 A simple TCP socket-based logging config receiver.
331 """
332
333 allow_reuse_address = 1
334
335 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000336 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000337 ThreadingTCPServer.__init__(self, (host, port), handler)
338 logging._acquireLock()
339 self.abort = 0
340 logging._releaseLock()
341 self.timeout = 1
342
343 def serve_until_stopped(self):
344 import select
345 abort = 0
346 while not abort:
347 rd, wr, ex = select.select([self.socket.fileno()],
348 [], [],
349 self.timeout)
350 if rd:
351 self.handle_request()
352 logging._acquireLock()
353 abort = self.abort
354 logging._releaseLock()
355
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000356 def serve(rcvr, hdlr, port):
357 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000358 global _listener
359 logging._acquireLock()
360 _listener = server
361 logging._releaseLock()
362 server.serve_until_stopped()
363
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000364 return threading.Thread(target=serve,
365 args=(ConfigSocketReceiver,
366 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000367
368def stopListening():
369 """
370 Stop the listening server which was created with a call to listen().
371 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000372 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000373 if _listener:
374 logging._acquireLock()
375 _listener.abort = 1
376 _listener = None
377 logging._releaseLock()