1

I'm trying to raise an exception with a stacktrace that doesn't include the last Frame.

In python 2 it was possible to remove the last entry from the strack like this:

def exceptionCatcher(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception:
            excInfo = sys.exc_info()
            # some custom logging stuff
            raise excInfo[0], excInfo[1], excInfo[2].tb_next
    return wrapper

def a():
    raise Exception("My Exception")

@exceptionCatcher
def test():
    a()

This worked in python and excluded the last exceptionCatcher call itself from the traceback e.g.

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    test()
  File "test.py", line 12, in a
    raise Exception("a")
Exception: a 

instead of

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    test()
  File "test.py", line 9, in wrapper
    raise e
  File "test.py", line 5, in wrapper
    return func(*args, **kwargs)
  File "test.py", line 10, in test
    a()
  File "test.py", line 5, in a
    raise Exception("a")
Exception: a

Switching to python3 it's not possible to raise an exception like: raise excInfo[0], excInfo[1], excInfo[2].tb_next

and i had to use:

def exceptionCatcher(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            excInfo = sys.exc_info()
            # some custom logging stuff
            raise e.with_traceback(excInfo[2].tb_next)
...  

Which added the following line to my traceback:

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    test()
  File "test.py", line 9, in wrapper
    raise e.with_traceback(excInfo[2].tb_next)
  File "test.py", line 10, in test
    a()
  File "test.py", line 5, in a
    raise Exception("a")
Exception: a

Is there any way to get rid of this part of the traceback?

I know exceptionCatching can be done with the exceptionhook but in the scenario i'm using this it's not possible to use the exceptionhook as it's another third party application (in cpp) which seems to catch the exception so the exceptionhook won't catch any. So i came up with the idea of using a wrapper decorator as shown above eventho it's a hacky way.

  • [How can I create an Exception in Python minus the last stack frame?](https://stackoverflow.com/questions/26993675/how-can-i-create-an-exception-in-python-minus-the-last-stack-frame) – GIZ Jan 31 '23 at 18:31
  • Do you really want to remove `a` from the traceback, or just your wrapper? Consider using a context manager, rather than a decorator, to customize the logging around an exception. – chepner Jan 31 '23 at 18:53
  • Yeah fair point, i might used a not-quite-correct term, yeah i want to get rid of the wrapper not the last frame in the traceback. I think the problem im having is that the top most frames in the traceback are added because the exception got thrown there. (While they were ignored in py2) – Jonas Sorgenfrei Jan 31 '23 at 20:41

1 Answers1

1

Instead of a decorator, use a context manager, which doesn't add a stack frame in the first place.

import contextlib


@contextlib.contextmanager
def exceptionCatcher():
    try:
        yield
    except Exception as e:
        print("custom logging")
        raise


def a():
    raise Exception("My Exception")


def test():
    with exceptionCatcher():
        a()


test()
chepner
  • 497,756
  • 71
  • 530
  • 681
  • That looks good, but i was looking for a decorator that it's easy to see in the code where i used the decorator and that i can easily remove it. In the futur i hope that the exception_hook connection form the application is getting a fix, so the decorator was meant as a temporary fix to catch exceptions. – Jonas Sorgenfrei Jan 31 '23 at 20:44
  • 1
    I don't see how the decorator is easier to find than a context manager; you're almost certainly going to use the search feature of your editor to find it, not just scan the code visually. – chepner Jan 31 '23 at 20:45
  • Yeah thats right the finding is the same. But removing a decorator seems to be more easy than then a context manager. Is there a way like a decorator that says the whole function should use the provided context? – Jonas Sorgenfrei Jan 31 '23 at 20:51
  • What i'm trying to do is. Given a lot of already implemented modules i was looking for an easy way to add this exception capture behaviour. While it seems to me that the context manager would mean that i have to edit the source code of every function. A decorator allows me to easily add it even dynamically using e.g.: ``` for k, v in globals().items(): if inspect.isfunction(v): modul_globals[k] = houdini_sentry_exception_catcher(v) ``` So this temporary adjustment should not mean a lot of adjustments in every function. – Jonas Sorgenfrei Jan 31 '23 at 20:56
  • OKay just saw that it's possible to use: ``` @exceptionCatcher() def test(): a() ``` But this has the same effect as my code above: ``` Traceback (most recent call last): File "d:\Desktop\test_context_manager.py", line 24, in test() File "C:\Users\Jonas\AppData\Local\Programs\Python\Python37\lib\contextlib.py", line 74, in inner return func(*args, **kwds) File "d:\Desktop\test_context_manager.py", line 21, in test a() File "d:\Desktop\test_context_manager.py", line 17, in a raise Exception("My Exception") Exception: My Exception ``` – Jonas Sorgenfrei Jan 31 '23 at 21:02
  • 1
    Yes, because the decorator can't just "inject" code into the existing function; it's implicitly defining a new function that wraps the existing function in a `with` statement. That's what I'm avoiding by using the `with` statement explicitly. – chepner Jan 31 '23 at 21:19
  • Yeha make sense, well then i guess there's not an easy way as i expected it to be. But thanks for your help! – Jonas Sorgenfrei Jan 31 '23 at 21:21
  • 1
    Might want hold off accepting my answer for now. (One with no accepted answer may attract more attention from someone who does know how to skip the frame you want.) – chepner Jan 31 '23 at 21:25
  • Allright thanks. as i understood better now, the frames are added to the stack after the point where i was able to modify it, but let's see may be there's actually a "hacky" way :) – Jonas Sorgenfrei Jan 31 '23 at 21:28