diff -r 5ef49659935f -r 8756372f0f5f Doc/library/logging.rst --- a/Doc/library/logging.rst Fri Dec 28 19:09:41 2012 +0100 +++ b/Doc/library/logging.rst Fri Dec 28 22:58:33 2012 +0000 @@ -992,6 +992,31 @@ returned. Otherwise, the string 'Level %s' % lvl is returned. +.. function:: skipCallers(modvars, skip=True) + + Allows the specification of modules which contain logging wrapper code, + which are to be skipped when determining which code made a logging call. + Since the information provided here needs to be cross-checked with + information available in stack frames when processing a logging call, the + ``globals()`` dictionary of the module is used (as it can be compared with + the ``f_globals`` member of a stack frame). You can use this function both + to register and unregister code from being skipped. The ``modvars`` value is + a module's namespace dictionary, accessed using :func:`globals` in a + module to register itself for skipping, or using *vars(some_module)* for + registering a different module to the one in which *skipCallers* is called. + If you omit *skip* or pass it as *True*, the *modvars* value is registered + for skipping: if you pass *False*, it is un-registered (if it has been + registered). + + Since dictionaries aren't hashable, they are stored in a list. Since + scanning lists for membership is slower than scanning sets or dicts, + registering a lot of modules using this function may adversely impact + performance. However, you should not need to worry if you only register a + handful of modules. + + .. versionadded:: 3.4 + + .. function:: makeLogRecord(attrdict) Creates and returns a new :class:`LogRecord` instance whose attributes are diff -r 5ef49659935f -r 8756372f0f5f Lib/logging/__init__.py --- a/Lib/logging/__init__.py Fri Dec 28 19:09:41 2012 +0100 +++ b/Lib/logging/__init__.py Fri Dec 28 22:58:33 2012 +0000 @@ -60,6 +60,36 @@ _srcfile = os.path.normcase(_srcfile) +_skipcallers = [] # can't hash dicts, so we keep a list. + +def skipCallers(modvars, skip=True): + """ + Identify modules to skip in findCallers by specifying the module's + globals() dictionary. Frames belonging to code in modules to be skipped + (e.g. wrapper code) are passed over when trying to locate which user + code invoked a logging call. + + Note that registering a lot of modules may impact performance adversely. + + :param modvars: The globals() dictionary of a module for which + frames are to be skipped in :meth:`findCaller`, typically + passed using ``vars(some_module)`` or ``globals()``. + :param skip: If True, the module is registered as one to be skipped. + Otherwise, the module is removed from the list of those + registered to be skipped. + """ + _acquireLock() + try: + if skip: + if modvars not in _skipcallers: + _skipcallers.append(modvars) + else: + if modvars in _skipcallers: + _skipcallers.remove(modvars) + finally: + _releaseLock() + + if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3) else: #pragma: no cover @@ -1317,10 +1347,20 @@ if f is not None: f = f.f_back rv = "(unknown file)", 0, "(unknown function)", None - while hasattr(f, "f_code"): - co = f.f_code - filename = os.path.normcase(co.co_filename) - if filename == _srcfile: + globs = globals() + while f is not None: + if f.f_globals is globs: + # always skip frames in this module + skip = True + elif f.f_globals in _skipcallers: + # skip frames in registered modules + skip = True + else: + # skip using older method + co = f.f_code + filename = os.path.normcase(co.co_filename) + skip = (filename == _srcfile) + if skip: f = f.f_back continue sinfo = None diff -r 5ef49659935f -r 8756372f0f5f Lib/test/test_logging.py --- a/Lib/test/test_logging.py Fri Dec 28 19:09:41 2012 +0100 +++ b/Lib/test/test_logging.py Fri Dec 28 22:58:33 2012 +0000 @@ -3641,6 +3641,18 @@ self.assertEqual(len(called), 1) self.assertEqual('Stack (most recent call last):\n', called[0]) + def test_find_callers_with_skip(self): + self.logger.debug('One') + globs = globals() + logging.skipCallers(globs, True) + self.logger.debug('Two') + logging.skipCallers(globs, False) + self.logger.debug('Three') + records = self.recording.records + self.assertEqual(records[0].pathname, __file__) + self.assertNotEqual(records[1].pathname, __file__) + self.assertEqual(records[2].pathname, __file__) + def test_make_record_with_extra_overwrite(self): name = 'my record' level = 13