diff -r d766589ed5a2 -r 58f0edef2b19 Doc/library/logging.rst --- a/Doc/library/logging.rst Fri Dec 27 17:01:16 2013 -0500 +++ b/Doc/library/logging.rst Sat Dec 28 08:01:15 2013 +0000 @@ -1048,6 +1048,22 @@ 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. 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.5 + + .. function:: makeLogRecord(attrdict) Creates and returns a new :class:`LogRecord` instance whose attributes are diff -r d766589ed5a2 -r 58f0edef2b19 Lib/logging/__init__.py --- a/Lib/logging/__init__.py Fri Dec 27 17:01:16 2013 -0500 +++ b/Lib/logging/__init__.py Sat Dec 28 08:01:15 2013 +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,34 @@ _srcfile = os.path.normcase(_srcfile) +_skipcallers = set() + +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`. 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: + if skip and modname: + _skipcallers.add(modname) + elif modname in _skipcallers: + _skipcallers.remove(modname) + finally: + _releaseLock() + + if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3) else: #pragma: no cover @@ -1326,10 +1355,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.get('__name__') 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 d766589ed5a2 -r 58f0edef2b19 Lib/test/test_logging.py --- a/Lib/test/test_logging.py Fri Dec 27 17:01:16 2013 -0500 +++ b/Lib/test/test_logging.py Sat Dec 28 08:01:15 2013 +0000 @@ -3775,6 +3775,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