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 Should work under Python versions >= 1.5.2, except that source line
23 information is not available unless 'sys._getframe()' is.
25 Copyright (C) 2001-2007 Vinay Sajip. All Rights Reserved.
27 To use, simply 'import logging' and log away!
30 import sys
, logging
, logging
.handlers
, string
, socket
, struct
, os
, traceback
, types
38 from SocketServer
import ThreadingTCPServer
, StreamRequestHandler
41 DEFAULT_LOGGING_CONFIG_PORT
= 9030
43 if sys
.platform
== "win32":
44 RESET_ERROR
= 10054 #WSAECONNRESET
46 RESET_ERROR
= 104 #ECONNRESET
49 # The following code implements a socket listener for on-the-fly
50 # reconfiguration of logging.
52 # _listener holds the server object doing the listening
55 def fileConfig(fname
, defaults
=None, disable_existing_loggers
=1):
57 Read the logging configuration from a ConfigParser-format file.
59 This can be called several times from an application, allowing an end user
60 the ability to select from various pre-canned configurations (if the
61 developer provides a mechanism to present the choices and load the chosen
63 In versions of ConfigParser which have the readfp method [typically
64 shipped in 2.x versions of Python], you can pass in a file-like object
65 rather than a filename, in which case the file-like object will be read
70 cp
= ConfigParser
.ConfigParser(defaults
)
71 if hasattr(cp
, 'readfp') and hasattr(fname
, 'readline'):
76 formatters
= _create_formatters(cp
)
79 logging
._acquireLock
()
81 logging
._handlers
.clear()
82 del logging
._handlerList
[:]
83 # Handlers add themselves to logging._handlers
84 handlers
= _install_handlers(cp
, formatters
)
85 _install_loggers(cp
, handlers
, disable_existing_loggers
)
87 logging
._releaseLock
()
91 """Resolve a dotted name to a global object."""
92 name
= string
.split(name
, '.')
94 found
= __import__(used
)
98 found
= getattr(found
, n
)
99 except AttributeError:
101 found
= getattr(found
, n
)
105 def _create_formatters(cp
):
106 """Create and return formatters"""
107 flist
= cp
.get("formatters", "keys")
110 flist
= string
.split(flist
, ",")
113 sectname
= "formatter_%s" % string
.strip(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
= string
.split(hlist
, ",")
140 fixups
= [] #for inter-handler references
142 sectname
= "handler_%s" % string
.strip(hand
)
143 klass
= cp
.get(sectname
, "class")
144 opts
= cp
.options(sectname
)
145 if "formatter" in opts
:
146 fmt
= cp
.get(sectname
, "formatter")
150 klass
= eval(klass
, vars(logging
))
151 except (AttributeError, NameError):
152 klass
= _resolve(klass
)
153 args
= cp
.get(sectname
, "args")
154 args
= eval(args
, vars(logging
))
155 h
= apply(klass
, args
)
157 level
= cp
.get(sectname
, "level")
158 h
.setLevel(logging
._levelNames
[level
])
160 h
.setFormatter(formatters
[fmt
])
161 if issubclass(klass
, logging
.handlers
.MemoryHandler
):
163 target
= cp
.get(sectname
,"target")
166 if len(target
): #the target handler may not be loaded yet, so keep for later...
167 fixups
.append((h
, target
))
169 #now all handlers are loaded, fixup inter-handler references...
171 h
.setTarget(handlers
[t
])
175 def _install_loggers(cp
, handlers
, disable_existing_loggers
):
176 """Create and install loggers"""
178 # configure the root first
179 llist
= cp
.get("loggers", "keys")
180 llist
= string
.split(llist
, ",")
181 llist
= map(lambda x
: string
.strip(x
), llist
)
183 sectname
= "logger_root"
186 opts
= cp
.options(sectname
)
188 level
= cp
.get(sectname
, "level")
189 log
.setLevel(logging
._levelNames
[level
])
190 for h
in root
.handlers
[:]:
191 root
.removeHandler(h
)
192 hlist
= cp
.get(sectname
, "handlers")
194 hlist
= string
.split(hlist
, ",")
196 log
.addHandler(handlers
[string
.strip(hand
)])
198 #and now the others...
199 #we don't want to lose the existing loggers,
200 #since other threads may have pointers to them.
201 #existing is set to contain all existing loggers,
202 #and as we go through the new configuration we
203 #remove any which are configured. At the end,
204 #what's left in existing is the set of loggers
205 #which were in the previous configuration but
206 #which are not in the new configuration.
207 existing
= root
.manager
.loggerDict
.keys()
208 #The list needs to be sorted so that we can
209 #avoid disabling child loggers of explicitly
210 #named loggers. With a sorted list it is easier
211 #to find the child loggers.
213 #We'll keep the list of existing loggers
214 #which are children of named loggers here...
216 #now set up the new ones...
218 sectname
= "logger_%s" % log
219 qn
= cp
.get(sectname
, "qualname")
220 opts
= cp
.options(sectname
)
221 if "propagate" in opts
:
222 propagate
= cp
.getint(sectname
, "propagate")
225 logger
= logging
.getLogger(qn
)
227 i
= existing
.index(qn
)
229 pflen
= len(prefixed
)
230 num_existing
= len(existing
)
231 i
= i
+ 1 # look at the entry after qn
232 while (i
< num_existing
) and (existing
[i
][:pflen
] == prefixed
):
233 child_loggers
.append(existing
[i
])
237 level
= cp
.get(sectname
, "level")
238 logger
.setLevel(logging
._levelNames
[level
])
239 for h
in logger
.handlers
[:]:
240 logger
.removeHandler(h
)
241 logger
.propagate
= propagate
243 hlist
= cp
.get(sectname
, "handlers")
245 hlist
= string
.split(hlist
, ",")
247 logger
.addHandler(handlers
[string
.strip(hand
)])
249 #Disable any old loggers. There's no point deleting
250 #them as other threads may continue to hold references
251 #and by disabling them, you stop them doing any logging.
252 #However, don't disable children of named loggers, as that's
253 #probably not what was intended by the user.
255 logger
= root
.manager
.loggerDict
[log
]
256 if log
in child_loggers
:
257 logger
.level
= logging
.NOTSET
260 elif disable_existing_loggers
:
264 def listen(port
=DEFAULT_LOGGING_CONFIG_PORT
):
266 Start up a socket server on the specified port, and listen for new
269 These will be sent as a file suitable for processing by fileConfig().
270 Returns a Thread object on which you can call start() to start the server,
271 and which you can join() when appropriate. To stop the server, call
275 raise NotImplementedError, "listen() needs threading to work"
277 class ConfigStreamHandler(StreamRequestHandler
):
279 Handler for a logging configuration request.
281 It expects a completely new logging configuration and uses fileConfig
288 Each request is expected to be a 4-byte length, packed using
289 struct.pack(">L", n), followed by the config file.
290 Uses fileConfig() to do the grunt work.
294 conn
= self
.connection
297 slen
= struct
.unpack(">L", chunk
)[0]
298 chunk
= self
.connection
.recv(slen
)
299 while len(chunk
) < slen
:
300 chunk
= chunk
+ conn
.recv(slen
- len(chunk
))
301 #Apply new configuration. We'd like to be able to
302 #create a StringIO and pass that in, but unfortunately
303 #1.5.2 ConfigParser does not support reading file
304 #objects, only actual files. So we create a temporary
305 #file and remove it later.
306 file = tempfile
.mktemp(".ini")
312 except (KeyboardInterrupt, SystemExit):
315 traceback
.print_exc()
317 except socket
.error
, e
:
318 if type(e
.args
) != types
.TupleType
:
322 if errcode
!= RESET_ERROR
:
325 class ConfigSocketReceiver(ThreadingTCPServer
):
327 A simple TCP socket-based logging config receiver.
330 allow_reuse_address
= 1
332 def __init__(self
, host
='localhost', port
=DEFAULT_LOGGING_CONFIG_PORT
,
334 ThreadingTCPServer
.__init
__(self
, (host
, port
), handler
)
335 logging
._acquireLock
()
337 logging
._releaseLock
()
340 def serve_until_stopped(self
):
344 rd
, wr
, ex
= select
.select([self
.socket
.fileno()],
348 self
.handle_request()
349 logging
._acquireLock
()
351 logging
._releaseLock
()
353 def serve(rcvr
, hdlr
, port
):
354 server
= rcvr(port
=port
, handler
=hdlr
)
356 logging
._acquireLock
()
358 logging
._releaseLock
()
359 server
.serve_until_stopped()
361 return threading
.Thread(target
=serve
,
362 args
=(ConfigSocketReceiver
,
363 ConfigStreamHandler
, port
))
367 Stop the listening server which was created with a call to listen().
371 logging
._acquireLock
()
374 logging
._releaseLock
()