1 # Copyright 2001-2007 by Vinay Sajip. All Rights Reserved.
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.
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.
22 Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved.
24 To use, simply 'import logging' and log away!
27 import sys
, logging
, logging
.handlers
, socket
, struct
, os
, traceback
35 from SocketServer
import ThreadingTCPServer
, StreamRequestHandler
38 DEFAULT_LOGGING_CONFIG_PORT
= 9030
40 if sys
.platform
== "win32":
41 RESET_ERROR
= 10054 #WSAECONNRESET
43 RESET_ERROR
= 104 #ECONNRESET
46 # The following code implements a socket listener for on-the-fly
47 # reconfiguration of logging.
49 # _listener holds the server object doing the listening
52 def fileConfig(fname
, defaults
=None, disable_existing_loggers
=True):
54 Read the logging configuration from a ConfigParser-format file.
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
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
67 cp
= ConfigParser
.ConfigParser(defaults
)
68 if hasattr(cp
, 'readfp') and hasattr(fname
, 'readline'):
73 formatters
= _create_formatters(cp
)
76 logging
._acquireLock
()
78 logging
._handlers
.clear()
79 del logging
._handlerList
[:]
80 # Handlers add themselves to logging._handlers
81 handlers
= _install_handlers(cp
, formatters
)
82 _install_loggers(cp
, handlers
, disable_existing_loggers
)
84 logging
._releaseLock
()
88 """Resolve a dotted name to a global object."""
89 name
= name
.split('.')
91 found
= __import__(used
)
95 found
= getattr(found
, n
)
96 except AttributeError:
98 found
= getattr(found
, n
)
101 def _strip_spaces(alist
):
102 return map(lambda x
: x
.strip(), alist
)
104 def _create_formatters(cp
):
105 """Create and return formatters"""
106 flist
= cp
.get("formatters", "keys")
109 flist
= flist
.split(",")
110 flist
= _strip_spaces(flist
)
113 sectname
= "formatter_%s" % form
114 opts
= cp
.options(sectname
)
116 fs
= cp
.get(sectname
, "format", 1)
119 if "datefmt" in opts
:
120 dfs
= cp
.get(sectname
, "datefmt", 1)
123 c
= logging
.Formatter
125 class_name
= cp
.get(sectname
, "class")
127 c
= _resolve(class_name
)
133 def _install_handlers(cp
, formatters
):
134 """Install and return handlers"""
135 hlist
= cp
.get("handlers", "keys")
138 hlist
= hlist
.split(",")
139 hlist
= _strip_spaces(hlist
)
141 fixups
= [] #for inter-handler references
143 sectname
= "handler_%s" % hand
144 klass
= cp
.get(sectname
, "class")
145 opts
= cp
.options(sectname
)
146 if "formatter" in opts
:
147 fmt
= cp
.get(sectname
, "formatter")
151 klass
= eval(klass
, vars(logging
))
152 except (AttributeError, NameError):
153 klass
= _resolve(klass
)
154 args
= cp
.get(sectname
, "args")
155 args
= eval(args
, vars(logging
))
158 level
= cp
.get(sectname
, "level")
159 h
.setLevel(logging
._levelNames
[level
])
161 h
.setFormatter(formatters
[fmt
])
162 if issubclass(klass
, logging
.handlers
.MemoryHandler
):
164 target
= cp
.get(sectname
,"target")
167 if len(target
): #the target handler may not be loaded yet, so keep for later...
168 fixups
.append((h
, target
))
170 #now all handlers are loaded, fixup inter-handler references...
172 h
.setTarget(handlers
[t
])
176 def _install_loggers(cp
, handlers
, disable_existing_loggers
):
177 """Create and install loggers"""
179 # configure the root first
180 llist
= cp
.get("loggers", "keys")
181 llist
= llist
.split(",")
182 llist
= list(map(lambda x
: x
.strip(), llist
))
184 sectname
= "logger_root"
187 opts
= cp
.options(sectname
)
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")
195 hlist
= hlist
.split(",")
196 hlist
= _strip_spaces(hlist
)
198 log
.addHandler(handlers
[hand
])
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.
209 existing
= list(root
.manager
.loggerDict
.keys())
210 #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.
215 #We'll keep the list of existing loggers
216 #which are children of named loggers here...
218 #now set up the new ones...
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")
227 logger
= logging
.getLogger(qn
)
229 i
= existing
.index(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
])
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
245 hlist
= cp
.get(sectname
, "handlers")
247 hlist
= hlist
.split(",")
248 hlist
= _strip_spaces(hlist
)
250 logger
.addHandler(handlers
[hand
])
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.
255 #However, don't disable children of named loggers, as that's
256 #probably not what was intended by the user.
258 logger
= root
.manager
.loggerDict
[log
]
259 if log
in child_loggers
:
260 logger
.level
= logging
.NOTSET
263 elif disable_existing_loggers
:
267 def listen(port
=DEFAULT_LOGGING_CONFIG_PORT
):
269 Start up a socket server on the specified port, and listen for new
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
278 raise NotImplementedError("listen() needs threading to work")
280 class ConfigStreamHandler(StreamRequestHandler
):
282 Handler for a logging configuration request.
284 It expects a completely new logging configuration and uses fileConfig
291 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.
297 conn
= self
.connection
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")
315 except (KeyboardInterrupt, SystemExit):
318 traceback
.print_exc()
320 except socket
.error
, e
:
321 if not isinstance(e
.args
, tuple):
325 if errcode
!= RESET_ERROR
:
328 class ConfigSocketReceiver(ThreadingTCPServer
):
330 A simple TCP socket-based logging config receiver.
333 allow_reuse_address
= 1
335 def __init__(self
, host
='localhost', port
=DEFAULT_LOGGING_CONFIG_PORT
,
337 ThreadingTCPServer
.__init
__(self
, (host
, port
), handler
)
338 logging
._acquireLock
()
340 logging
._releaseLock
()
343 def serve_until_stopped(self
):
347 rd
, wr
, ex
= select
.select([self
.socket
.fileno()],
351 self
.handle_request()
352 logging
._acquireLock
()
354 logging
._releaseLock
()
356 def serve(rcvr
, hdlr
, port
):
357 server
= rcvr(port
=port
, handler
=hdlr
)
359 logging
._acquireLock
()
361 logging
._releaseLock
()
362 server
.serve_until_stopped()
364 return threading
.Thread(target
=serve
,
365 args
=(ConfigSocketReceiver
,
366 ConfigStreamHandler
, port
))
370 Stop the listening server which was created with a call to listen().
374 logging
._acquireLock
()
377 logging
._releaseLock
()