Exception traceback is hidden if not re-raised immediately

Question:

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
Asked By: parxier

||

Answers:

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
Answered By: Senthil Kumaran

Your main function needs to look like this:

def main():
    try:
        func1()
    except Exception, err:
        # error processing
        raise

This is the standard way of handling (and re-raising) errors. Here is a codepad demonstration.

Answered By: Gabi Purcaru

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

Answered By: Jochen Ritzel

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, a TypeError exception is raised indicating that this is
an error (if running under IDLE, a Queue.Empty exception is raised
instead).

Otherwise, raise evaluates the expressions to get three objects, using
None 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 or None, a TypeError exception is raised.

The three-expression
form of raise is useful to re-raise an exception transparently in an
except clause, but raise 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]
Answered By: qris

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:

  1. get the original exc_info
  2. format the original error message, with stack trace
  3. 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)
Answered By: tvt173

In Python 3:

import sys

class CustomError(Exception):
    pass

try:
    code_throwing_an_exception()
except Exception as e:
    _, value, traceback = sys.exc_info()
    raise CustomError("A new Exception was raised: %s" % value).with_traceback(traceback)
Answered By: Omid Ariyan