blob: 4123506ffa3c48286dbdaf0b5277532c206a08bf [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])
Benjamin Peterson41181742008-07-02 20:22:54 +0000155 if issubclass(klass, logging.handlers.MemoryHandler):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000156 if "target" in opts:
157 target = cp.get(sectname,"target")
158 else:
159 target = ""
160 if len(target): #the target handler may not be loaded yet, so keep for later...
161 fixups.append((h, target))
162 handlers[hand] = h
163 #now all handlers are loaded, fixup inter-handler references...
164 for h, t in fixups:
165 h.setTarget(handlers[t])
166 return handlers
167
168
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000169def _install_loggers(cp, handlers, disable_existing_loggers):
Vinay Sajip989b69a2006-01-16 21:28:37 +0000170 """Create and install loggers"""
171
172 # configure the root first
173 llist = cp.get("loggers", "keys")
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000174 llist = llist.split(",")
Guido van Rossumc1f779c2007-07-03 08:25:58 +0000175 llist = list(map(lambda x: x.strip(), llist))
Vinay Sajip989b69a2006-01-16 21:28:37 +0000176 llist.remove("root")
177 sectname = "logger_root"
178 root = logging.root
179 log = root
180 opts = cp.options(sectname)
181 if "level" in opts:
182 level = cp.get(sectname, "level")
183 log.setLevel(logging._levelNames[level])
184 for h in root.handlers[:]:
185 root.removeHandler(h)
186 hlist = cp.get(sectname, "handlers")
187 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000188 hlist = hlist.split(",")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000189 for hand in hlist:
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000190 log.addHandler(handlers[hand.strip()])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000191
192 #and now the others...
193 #we don't want to lose the existing loggers,
194 #since other threads may have pointers to them.
195 #existing is set to contain all existing loggers,
196 #and as we go through the new configuration we
197 #remove any which are configured. At the end,
198 #what's left in existing is the set of loggers
199 #which were in the previous configuration but
200 #which are not in the new configuration.
Guido van Rossum8b8a5432007-02-12 00:07:01 +0000201 existing = list(root.manager.loggerDict.keys())
Christian Heimes96f31632007-11-12 01:32:03 +0000202 #The list needs to be sorted so that we can
203 #avoid disabling child loggers of explicitly
204 #named loggers. With a sorted list it is easier
205 #to find the child loggers.
206 existing.sort()
207 #We'll keep the list of existing loggers
208 #which are children of named loggers here...
209 child_loggers = []
Vinay Sajip989b69a2006-01-16 21:28:37 +0000210 #now set up the new ones...
211 for log in llist:
212 sectname = "logger_%s" % log
213 qn = cp.get(sectname, "qualname")
214 opts = cp.options(sectname)
215 if "propagate" in opts:
216 propagate = cp.getint(sectname, "propagate")
217 else:
218 propagate = 1
219 logger = logging.getLogger(qn)
220 if qn in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000221 i = existing.index(qn)
222 prefixed = qn + "."
223 pflen = len(prefixed)
224 num_existing = len(existing)
225 i = i + 1 # look at the entry after qn
226 while (i < num_existing) and (existing[i][:pflen] == prefixed):
227 child_loggers.append(existing[i])
228 i = i + 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000229 existing.remove(qn)
230 if "level" in opts:
231 level = cp.get(sectname, "level")
232 logger.setLevel(logging._levelNames[level])
233 for h in logger.handlers[:]:
234 logger.removeHandler(h)
235 logger.propagate = propagate
236 logger.disabled = 0
237 hlist = cp.get(sectname, "handlers")
238 if len(hlist):
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000239 hlist = hlist.split(",")
Vinay Sajip989b69a2006-01-16 21:28:37 +0000240 for hand in hlist:
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000241 logger.addHandler(handlers[hand.strip()])
Vinay Sajip989b69a2006-01-16 21:28:37 +0000242
243 #Disable any old loggers. There's no point deleting
244 #them as other threads may continue to hold references
245 #and by disabling them, you stop them doing any logging.
Christian Heimes96f31632007-11-12 01:32:03 +0000246 #However, don't disable children of named loggers, as that's
247 #probably not what was intended by the user.
Vinay Sajip989b69a2006-01-16 21:28:37 +0000248 for log in existing:
Christian Heimes96f31632007-11-12 01:32:03 +0000249 logger = root.manager.loggerDict[log]
250 if log in child_loggers:
251 logger.level = logging.NOTSET
252 logger.handlers = []
253 logger.propagate = 1
Benjamin Petersonfea6a942008-07-02 16:11:42 +0000254 elif disable_existing_loggers:
Christian Heimes96f31632007-11-12 01:32:03 +0000255 logger.disabled = 1
Vinay Sajip989b69a2006-01-16 21:28:37 +0000256
257
Guido van Rossum57102f82002-11-13 16:15:58 +0000258def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
259 """
260 Start up a socket server on the specified port, and listen for new
261 configurations.
262
263 These will be sent as a file suitable for processing by fileConfig().
264 Returns a Thread object on which you can call start() to start the server,
265 and which you can join() when appropriate. To stop the server, call
266 stopListening().
267 """
268 if not thread:
Collin Winterce36ad82007-08-30 01:19:48 +0000269 raise NotImplementedError("listen() needs threading to work")
Guido van Rossum57102f82002-11-13 16:15:58 +0000270
271 class ConfigStreamHandler(StreamRequestHandler):
272 """
273 Handler for a logging configuration request.
274
275 It expects a completely new logging configuration and uses fileConfig
276 to install it.
277 """
278 def handle(self):
279 """
280 Handle a request.
281
Vinay Sajip4c1423b2005-06-05 20:39:36 +0000282 Each request is expected to be a 4-byte length, packed using
283 struct.pack(">L", n), followed by the config file.
284 Uses fileConfig() to do the grunt work.
Guido van Rossum57102f82002-11-13 16:15:58 +0000285 """
286 import tempfile
287 try:
288 conn = self.connection
289 chunk = conn.recv(4)
290 if len(chunk) == 4:
291 slen = struct.unpack(">L", chunk)[0]
292 chunk = self.connection.recv(slen)
293 while len(chunk) < slen:
294 chunk = chunk + conn.recv(slen - len(chunk))
295 #Apply new configuration. We'd like to be able to
296 #create a StringIO and pass that in, but unfortunately
297 #1.5.2 ConfigParser does not support reading file
298 #objects, only actual files. So we create a temporary
299 #file and remove it later.
300 file = tempfile.mktemp(".ini")
301 f = open(file, "w")
302 f.write(chunk)
303 f.close()
Vinay Sajip989b69a2006-01-16 21:28:37 +0000304 try:
305 fileConfig(file)
306 except (KeyboardInterrupt, SystemExit):
307 raise
308 except:
309 traceback.print_exc()
Guido van Rossum57102f82002-11-13 16:15:58 +0000310 os.remove(file)
Guido van Rossumb940e112007-01-10 16:19:56 +0000311 except socket.error as e:
Guido van Rossum13257902007-06-07 23:15:56 +0000312 if not isinstancetype(e.args, tuple):
Guido van Rossum57102f82002-11-13 16:15:58 +0000313 raise
314 else:
315 errcode = e.args[0]
316 if errcode != RESET_ERROR:
317 raise
318
319 class ConfigSocketReceiver(ThreadingTCPServer):
320 """
321 A simple TCP socket-based logging config receiver.
322 """
323
324 allow_reuse_address = 1
325
326 def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000327 handler=None):
Guido van Rossum57102f82002-11-13 16:15:58 +0000328 ThreadingTCPServer.__init__(self, (host, port), handler)
329 logging._acquireLock()
330 self.abort = 0
331 logging._releaseLock()
332 self.timeout = 1
333
334 def serve_until_stopped(self):
335 import select
336 abort = 0
337 while not abort:
338 rd, wr, ex = select.select([self.socket.fileno()],
339 [], [],
340 self.timeout)
341 if rd:
342 self.handle_request()
343 logging._acquireLock()
344 abort = self.abort
345 logging._releaseLock()
346
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000347 def serve(rcvr, hdlr, port):
348 server = rcvr(port=port, handler=hdlr)
Guido van Rossum57102f82002-11-13 16:15:58 +0000349 global _listener
350 logging._acquireLock()
351 _listener = server
352 logging._releaseLock()
353 server.serve_until_stopped()
354
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000355 return threading.Thread(target=serve,
356 args=(ConfigSocketReceiver,
357 ConfigStreamHandler, port))
Guido van Rossum57102f82002-11-13 16:15:58 +0000358
359def stopListening():
360 """
361 Stop the listening server which was created with a call to listen().
362 """
Neal Norwitzc4d047a2002-11-15 23:33:20 +0000363 global _listener
Guido van Rossum57102f82002-11-13 16:15:58 +0000364 if _listener:
365 logging._acquireLock()
366 _listener.abort = 1
367 _listener = None
368 logging._releaseLock()