diff -r 1bed43c0a5af -r aecafebb7880 Doc/library/logging.rst --- a/Doc/library/logging.rst Sat Dec 29 21:46:37 2012 +0200 +++ b/Doc/library/logging.rst Sat Dec 29 20:29:37 2012 +0000 @@ -992,6 +992,23 @@ returned. Otherwise, the string 'Level %s' % lvl is returned. +.. function:: skipCallers(modname, 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. + You can use this function both to register and unregister code from being + skipped. The ``modname`` value is a module's name; the module should have + been imported and be present in ``sys.modules``. If you omit *skip* or pass + it as ``True``, the ``modname`` value is registered for skipping: if you + pass ``False``, it is un-registered (if it has been registered). + + 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 1bed43c0a5af -r aecafebb7880 Lib/logging/__init__.py --- a/Lib/logging/__init__.py Sat Dec 29 21:46:37 2012 +0200 +++ b/Lib/logging/__init__.py Sat Dec 29 20:29:37 2012 +0000 @@ -33,7 +33,8 @@ 'captureWarnings', 'critical', 'debug', 'disable', 'error', 'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass', 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning', - 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort'] + 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort', + 'skipCallers'] try: import threading @@ -60,6 +61,38 @@ _srcfile = os.path.normcase(_srcfile) +_skipcallers = [] # can't hash dicts, so we keep a list. + +def skipCallers(modname, skip=True): + """ + Identify modules to skip in findCallers by specifying the module's + name. 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 modname: The name of a module for which frames are to be skipped in + :meth:`findCaller`. The module should already have been + imported and be present in ``sys.modules``. You should be + able to pass ``__name__`` to skip the current module (the + one in which ``skipCallers`` is called). + :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: + modvars = vars(sys.modules[modname]) + 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 +1350,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 1bed43c0a5af -r aecafebb7880 Lib/test/test_logging.py --- a/Lib/test/test_logging.py Sat Dec 29 21:46:37 2012 +0200 +++ b/Lib/test/test_logging.py Sat Dec 29 20:29:37 2012 +0000 @@ -3641,6 +3641,17 @@ 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') + logging.skipCallers(__name__, True) + self.logger.debug('Two') + logging.skipCallers(__name__, 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