Frayt
Frayt

Reputation: 1233

Full traceback for chained exceptions when using 'raise from' syntax

Given the file errors.py:

from traceback import format_tb, extract_tb

class MyError(Exception):
    def __init__(self, message):
        self.message = message

class MySecondError(Exception):
    def __init__(self, message):
        self.message = message

try:
    try:
        raise MyError("Something specific has happened")
    except Exception as error:
        raise MySecondError("Something general has happened") from error
except Exception as error:
    print("".join(format_tb(error.__traceback__)))

When running python errors.py the output is:

  File "errors.py", line 15, in <module>
    raise MySecondError("Something general has happened") from error

The main problem with this is that only the traceback of the 'highest' error in the 'chain' (MySecondError) and there is no info on the wrapped error (MyError)

If I remove the final try/except wrapper so that the chained error is not caught, I get a much better output:

Traceback (most recent call last):
  File "exceptions.py", line 14, in <module>
    raise MyError("Something specific has happened")
__main__.MyError: Something specific has happened

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "exceptions.py", line 16, in <module>
    raise MySecondError("Something general has happened") from error
__main__.MySecondError: Something general has happened

Which has the traceback of the full error chain and conjoining lines (Traceback (most recent call last):, The above exception was the direct cause of the following exception:) and string representation of each error

Ideally I would like to capture these lines of output to direct them elsewhere (e.g. a logger)

One solution I have is to iterate over error.__context__ and manually add the conjoining phrases:

except Exception as error:
    inner_error = error

    while inner_error:
        if inner_error is not error:
            print("\nThe above exception was the direct cause of the following exception:\n")

        print("Traceback (most recent call last):")
        print("".join(format_tb(inner_error.__traceback__) + [ str(error) ])) 
        inner_error = inner_error.__context__

Which works, but it is hacky and I would prefer to use some standard-library module which already handles this.

Upvotes: 0

Views: 668

Answers (1)

Bakuriu
Bakuriu

Reputation: 101979

You want to use the format_exception function:

from traceback import format_tb, format_exception

class MyError(Exception):
    def __init__(self, message):
        self.message = message

class MySecondError(Exception):
    def __init__(self, message):
        self.message = message

try:
    try:
        raise MyError("Something specific has happened")
    except Exception as error:
        raise MySecondError("Something general has happened") from error
except Exception as error:
    print("".join(format_exception(error.__class__, error, error.__traceback__)))

Gives:

$ python3 /tmp/a.py 
Traceback (most recent call last):
  File "/tmp/a.py", line 13, in <module>
    raise MyError("Something specific has happened")
MyError: Something specific has happened

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/tmp/a.py", line 15, in <module>
    raise MySecondError("Something general has happened") from error
MySecondError: Something general has happened

If you specify chain=False this function will not print conjoining exceptions but only the last one.

Upvotes: 1

Related Questions