Exception traceback is hidden if not re-raised immediately
I've got a piece of code similar to this:
import sys
def func1():
func2()
def func2():
raise Exception('test error')
def main():
err = None
try:
func1()
except:
err = sys.exc_info()[1]
pass
# some extra processing, involving checking err details (if err is not None)
# need to re-raise err so caller can do its own handling
if err:
raise err
if __name__ == '__main__':
main()
When func2
raises an exception I receive the following traceback:
Traceback (most recent call last):
File "err_test.py", line 25, in <module>
main()
File "err_test.py", line 22, in main
raise err
Exception: test error
From here I don't see where the exception is coming from. The original traceback is lost.
How can I preserve original traceback and re-raise it? I want to see something similar to this:
Traceback (most recent call last):
File "err_test.py", line 26, in <module>
main()
File "err_test.py", line 13, in main
func1()
File "err_test.py", line 4, in func1
func2()
File "err_test.py", line 7, in func2
raise Exception('test error')
Exception: test error
Solution 1:
A blank raise
raises the last exception.
# need to re-raise err so caller can do its own handling
if err:
raise
If you use raise something
Python has no way of knowing if something
was an exception just caught before, or a new exception with a new stack trace. That's why there is the blank raise
that preserves the stack trace.
Reference here
Solution 2:
It is possible to modify and rethrow an exception:
If no expressions are present,
raise
re-raises the last exception that was active in the current scope. If no exception is active in the current scope, aTypeError
exception is raised indicating that this is an error (if running under IDLE, aQueue.Empty
exception is raised instead).Otherwise,
raise
evaluates the expressions to get three objects, usingNone
as the value of omitted expressions. The first two objects are used to determine the type and value of the exception.If a third object is present and not
None
, it must be a traceback object (see section The standard type hierarchy), and it is substituted instead of the current location as the place where the exception occurred. If the third object is present and not a traceback object orNone
, aTypeError
exception is raised.The three-expression form of
raise
is useful to re-raise an exception transparently in anexcept
clause, butraise
with no expressions should be preferred if the exception to be re-raised was the most recently active exception in the current scope.
So if you want to modify the exception and rethrow it, you can do this:
try:
buggy_code_which_throws_exception()
except Exception as e:
raise Exception, "The code is buggy: %s" % e, sys.exc_info()[2]
Solution 3:
You can get a lot of information about the exception via the sys.exc_info()
along with the traceback module
try the following extension to your code.
import sys
import traceback
def func1():
func2()
def func2():
raise Exception('test error')
def main():
try:
func1()
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Do your verification using exc_value and exc_traceback
print "*** print_exception:"
traceback.print_exception(exc_type, exc_value, exc_traceback,
limit=3, file=sys.stdout)
if __name__ == '__main__':
main()
This would print, similar to what you wanted.
*** print_exception:
Traceback (most recent call last):
File "err_test.py", line 14, in main
func1()
File "err_test.py", line 5, in func1
func2()
File "err_test.py", line 8, in func2
raise Exception('test error')
Exception: test error
Solution 4:
While @Jochen's answer works well in the simple case, it is not capable of handling more complex cases, where you are not directly catching and rethrowing, but are for some reason given the exception as an object and wish to re-throw in a completely new context (i.e. if you need to handle it in a different process).
In this case, I propose the following:
- get the original exc_info
- format the original error message, with stack trace
- throw a new exception with that full error message (stack trace incl.) embedded
Before you do this, define a new exception type that you will rethrow later...
class ChildTaskException(Exception):
pass
In the offending code...
import sys
import traceback
try:
# do something dangerous
except:
error_type, error, tb = sys.exc_info()
error_lines = traceback.format_exception(error_type, error, tb)
error_msg = ''.join(error_lines)
# for example, if you are doing multiprocessing, you might want to send this to another process via a pipe
connection.send(error_msg)
Rethrow...
# again, a multiprocessing example of receiving that message through a pipe
error_msg = pcon.recv()
raise ChildTaskException(error_msg)