Exception traceback is hidden if not re-raised immediately - python

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

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

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]

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

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)

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.

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)

Related

How to print the function that raised an exception? [duplicate]

I want to catch and log exceptions without exiting, e.g.,
try:
do_stuff()
except Exception as err:
print(Exception, err)
# I want to print the entire traceback here,
# not just the exception name and details
I want to print the exact same output that is printed when the exception is raised without the try/except intercepting the exception, and I do not want it to exit my program.
traceback.format_exc() or sys.exc_info() will yield more info if that's what you want.
import traceback
import sys
try:
do_stuff()
except Exception:
print(traceback.format_exc())
# or
print(sys.exc_info()[2])
Some other answer have already pointed out the traceback module.
Please notice that with print_exc, in some corner cases, you will not obtain what you would expect. In Python 2.x:
import traceback
try:
raise TypeError("Oups!")
except Exception, err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_exc()
...will display the traceback of the last exception:
Traceback (most recent call last):
File "e.py", line 7, in <module>
raise TypeError("Again !?!")
TypeError: Again !?!
If you really need to access the original traceback one solution is to cache the exception infos as returned from exc_info in a local variable and display it using print_exception:
import traceback
import sys
try:
raise TypeError("Oups!")
except Exception, err:
try:
exc_info = sys.exc_info()
# do you usefull stuff here
# (potentially raising an exception)
try:
raise TypeError("Again !?!")
except:
pass
# end of useful stuff
finally:
# Display the *original* exception
traceback.print_exception(*exc_info)
del exc_info
Producing:
Traceback (most recent call last):
File "t.py", line 6, in <module>
raise TypeError("Oups!")
TypeError: Oups!
Few pitfalls with this though:
From the doc of sys_info:
Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement)
but, from the same doc:
Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.
On the other hand, by allowing you to access the traceback associated with an exception, Python 3 produce a less surprising result:
import traceback
try:
raise TypeError("Oups!")
except Exception as err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_tb(err.__traceback__)
... will display:
File "e3.py", line 4, in <module>
raise TypeError("Oups!")
If you're debugging and just want to see the current stack trace, you can simply call:
traceback.print_stack()
There's no need to manually raise an exception just to catch it again.
How to print the full traceback without halting the program?
When you don't want to halt your program on an error, you need to handle that error with a try/except:
try:
do_something_that_might_error()
except Exception as error:
handle_the_error(error)
To extract the full traceback, we'll use the traceback module from the standard library:
import traceback
And to create a decently complicated stacktrace to demonstrate that we get the full stacktrace:
def raise_error():
raise RuntimeError('something bad happened!')
def do_something_that_might_error():
raise_error()
Printing
To print the full traceback, use the traceback.print_exc method:
try:
do_something_that_might_error()
except Exception as error:
traceback.print_exc()
Which prints:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Better than printing, logging:
However, a best practice is to have a logger set up for your module. It will know the name of the module and be able to change levels (among other attributes, such as handlers)
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
In which case, you'll want the logger.exception function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.exception(error)
Which logs:
ERROR:__main__:something bad happened!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Or perhaps you just want the string, in which case, you'll want the traceback.format_exc function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.debug(traceback.format_exc())
Which logs:
DEBUG:__main__:Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Conclusion
And for all three options, we see we get the same output as when we have an error:
>>> do_something_that_might_error()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Which to use
Performance concerns aren't important here as IO usually dominates. I'd prefer, since it does precisely what's being requested in a forward compatible way:
logger.exception(error)
Logging levels and outputs can be adjusted, making it easy to turn off without touching the code. And usually doing what's directly needed is the most efficient way to do it.
First, do not use prints for logging, there is a stable, proven and well-thought out stdlib module to do that: logging. You definitely should use it instead.
Second, do not be tempted to do a mess with unrelated tools when there is a native and simple approach. Here it is:
log = logging.getLogger(__name__)
try:
call_code_that_fails()
except MyError:
log.exception('Any extra info you want to see in your logs')
That's it. You are done now.
Explanation for anyone who is interested in how things work under the hood
What log.exception is actually doing is just a call to log.error (that is, log event with level ERROR) and print traceback then.
Why is it better?
Well, here are some considerations:
it is just right;
it is straightforward;
it is simple.
Why should nobody use traceback or call logger with exc_info=True or get their hands dirty with sys.exc_info?
Well, just because! They all exist for different purposes. For example, traceback.print_exc's output is a little bit different from tracebacks produced by the interpreter itself. If you use it, you will confuse anyone who reads your logs, they will be banging their heads against them.
Passing exc_info=True to log calls is just inappropriate. But, it is useful when catching recoverable errors and you want to log them (using, e.g INFO level) with tracebacks as well, because log.exception produces logs of only one level - ERROR.
And you definitely should avoid messing with sys.exc_info as much as you can. It's just not a public interface, it's an internal one - you can use it if you definitely know what you are doing. It is not intended for just printing exceptions.
traceback.format_exception(exception_object)
If you only have the exception object, you can get the traceback as a string from any point of the code in Python 3 with:
import traceback
''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
Full example:
#!/usr/bin/env python3
import traceback
def f():
g()
def g():
raise Exception('asdf')
try:
g()
except Exception as e:
exc_obj = e
tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)
Output:
Traceback (most recent call last):
File "./main.py", line 12, in <module>
g()
File "./main.py", line 9, in g
raise Exception('asdf')
Exception: asdf
Documentation: https://docs.python.org/3.9/library/traceback.html#traceback.format_exception
See also: Extract traceback info from an exception object
Tested in Python 3.9
In addition to Aaron Hall's answer, if you are logging, but don't want to use logging.exception() (since it logs at the ERROR level), you can use a lower level and pass exc_info=True. e.g.
try:
do_something_that_might_error()
except Exception:
logging.info('General exception noted.', exc_info=True)
I don't see this mentioned in any of the other answers. If you're passing around an Exception object for whatever reason...
In Python 3.5+ you can get a trace from an Exception object using traceback.TracebackException.from_exception(). For example:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
try:
stack_lvl_3()
except Exception as e:
# raise
return e
def stack_lvl_1():
e = stack_lvl_2()
return e
e = stack_lvl_1()
tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))
However, the above code results in:
Traceback (most recent call last):
File "exc.py", line 10, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')
This is just two levels of the stack, as opposed to what would have been printed on screen had the exception been raised in stack_lvl_2() and not intercepted (uncomment the # raise line).
As I understand it, that's because an exception records only the current level of the stack when it is raised, stack_lvl_3() in this case. As it's passed back up through the stack, more levels are being added to its __traceback__. But we intercepted it in stack_lvl_2(), meaning all it got to record was levels 3 and 2. To get the full trace as printed on stdout we'd have to catch it at the highest (lowest?) level:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
stack_lvl_3()
def stack_lvl_1():
stack_lvl_2()
try:
stack_lvl_1()
except Exception as exc:
tb = traceback.TracebackException.from_exception(exc)
print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))
Which results in:
Handled at stack lvl 0
File "exc.py", line 17, in <module>
stack_lvl_1()
File "exc.py", line 13, in stack_lvl_1
stack_lvl_2()
File "exc.py", line 9, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Notice that the stack print is different, the first and last lines are missing. Because it's a different format().
Intercepting the exception as far away from the point where it was raised as possible makes for simpler code while also giving more information.
In python3 (works in 3.9) we can define a function and can use that where ever we want to print the details.
import traceback
def get_traceback(e):
lines = traceback.format_exception(type(e), e, e.__traceback__)
return ''.join(lines)
try:
1/0
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
try:
spam(1,2)
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
The output would be like:
bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
1/0
ZeroDivisionError: division by zero
------End--------
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
spam(1,2)
NameError: name 'spam' is not defined
------End--------
If you have an Error object already, and you want to print the whole thing, you need to make this slightly awkward call:
import traceback
traceback.print_exception(type(err), err, err.__traceback__)
That's right, print_exception takes three positional arguments: The type of the exception, the actual exception object, and the exception's own internal traceback property.
In python 3.5 or later, the type(err) is optional... but it's a positional argument, so you still have to explicitly pass None in its place.
traceback.print_exception(None, err, err.__traceback__)
I have no idea why all of this isn't just traceback.print_exception(err). Why you would ever want to print out an error, along with a traceback other than the one that belongs to that error, is beyond me.
To get the precise stack trace, as a string, that would have been raised if no try/except were there to step over it, simply place this in the except block that catches the offending exception.
desired_trace = traceback.format_exc(sys.exc_info())
Here's how to use it (assuming flaky_func is defined, and log calls your favorite logging system):
import traceback
import sys
try:
flaky_func()
except KeyboardInterrupt:
raise
except Exception:
desired_trace = traceback.format_exc(sys.exc_info())
log(desired_trace)
It's a good idea to catch and re-raise KeyboardInterrupts, so that you can still kill the program using Ctrl-C. Logging is outside the scope of the question, but a good option is logging. Documentation for the sys and traceback modules.
You will need to put the try/except inside the most innerloop where the error may occur, i.e.
for i in something:
for j in somethingelse:
for k in whatever:
try:
something_complex(i, j, k)
except Exception, e:
print e
try:
something_less_complex(i, j)
except Exception, e:
print e
... and so on
In other words, you will need to wrap statements that may fail in try/except as specific as possible, in the most inner-loop as possible.
A remark about this answer's comments: print(traceback.format_exc()) does a better job for me than traceback.print_exc(). With the latter, the hello is sometimes strangely "mixed" with the traceback text, like if both want to write to stdout or stderr at the same time, producing weird output (at least when building from inside a text editor and viewing the output in the "Build results" panel).
Traceback (most recent call last):
File "C:\Users\User\Desktop\test.py", line 7, in
hell do_stuff()
File "C:\Users\User\Desktop\test.py", line 4, in do_stuff
1/0
ZeroDivisionError: integer division or modulo by zero
o
[Finished in 0.1s]
So I use:
import traceback, sys
def do_stuff():
1/0
try:
do_stuff()
except Exception:
print(traceback.format_exc())
print('hello')
import io
import traceback
try:
call_code_that_fails()
except:
errors = io.StringIO()
traceback.print_exc(file=errors) # Instead of printing directly to stdout, the result can be further processed
contents = str(errors.getvalue())
print(contents)
errors.close()
You want the traceback module. It will let you print stack dumps like Python normally does. In particular, the print_last function will print the last exception and a stack trace.
python 3 solution
stacktrace_helper.py:
from linecache import getline
import sys
import traceback
def get_stack_trace():
exc_type, exc_value, exc_tb = sys.exc_info()
trace = traceback.format_stack()
trace = list(filter(lambda x: ("\\lib\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))
ex_type = exc_type.__name__
ex_line = exc_tb.tb_lineno
ex_file = exc_tb.tb_frame.f_code.co_filename
ex_message = str(exc_value)
line_code = ""
try:
line_code = getline(ex_file, ex_line).strip()
except:
pass
trace.insert(
0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',
)
return trace
def get_stack_trace_str(msg: str = ""):
trace = list(get_stack_trace())
trace_str = "\n".join(list(map(str, trace)))
trace_str = msg + "\n" + trace_str
return trace_str
This is my solution to write the error in a log file and also on console:
import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
exc_info=(exc_type, exc_value, exc_traceback)
logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
print("An error occured, check error.log to see the error details")
traceback.print_exception(*exc_info)
sys.excepthook = handle_exception
You could do:
try:
do_stuff()
except Exception, err:
print(Exception, err)
raise err

Tornado returning stacktrace on 500 server error [duplicate]

I want to catch and log exceptions without exiting, e.g.,
try:
do_stuff()
except Exception as err:
print(Exception, err)
# I want to print the entire traceback here,
# not just the exception name and details
I want to print the exact same output that is printed when the exception is raised without the try/except intercepting the exception, and I do not want it to exit my program.
traceback.format_exc() or sys.exc_info() will yield more info if that's what you want.
import traceback
import sys
try:
do_stuff()
except Exception:
print(traceback.format_exc())
# or
print(sys.exc_info()[2])
Some other answer have already pointed out the traceback module.
Please notice that with print_exc, in some corner cases, you will not obtain what you would expect. In Python 2.x:
import traceback
try:
raise TypeError("Oups!")
except Exception, err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_exc()
...will display the traceback of the last exception:
Traceback (most recent call last):
File "e.py", line 7, in <module>
raise TypeError("Again !?!")
TypeError: Again !?!
If you really need to access the original traceback one solution is to cache the exception infos as returned from exc_info in a local variable and display it using print_exception:
import traceback
import sys
try:
raise TypeError("Oups!")
except Exception, err:
try:
exc_info = sys.exc_info()
# do you usefull stuff here
# (potentially raising an exception)
try:
raise TypeError("Again !?!")
except:
pass
# end of useful stuff
finally:
# Display the *original* exception
traceback.print_exception(*exc_info)
del exc_info
Producing:
Traceback (most recent call last):
File "t.py", line 6, in <module>
raise TypeError("Oups!")
TypeError: Oups!
Few pitfalls with this though:
From the doc of sys_info:
Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement)
but, from the same doc:
Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.
On the other hand, by allowing you to access the traceback associated with an exception, Python 3 produce a less surprising result:
import traceback
try:
raise TypeError("Oups!")
except Exception as err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_tb(err.__traceback__)
... will display:
File "e3.py", line 4, in <module>
raise TypeError("Oups!")
If you're debugging and just want to see the current stack trace, you can simply call:
traceback.print_stack()
There's no need to manually raise an exception just to catch it again.
How to print the full traceback without halting the program?
When you don't want to halt your program on an error, you need to handle that error with a try/except:
try:
do_something_that_might_error()
except Exception as error:
handle_the_error(error)
To extract the full traceback, we'll use the traceback module from the standard library:
import traceback
And to create a decently complicated stacktrace to demonstrate that we get the full stacktrace:
def raise_error():
raise RuntimeError('something bad happened!')
def do_something_that_might_error():
raise_error()
Printing
To print the full traceback, use the traceback.print_exc method:
try:
do_something_that_might_error()
except Exception as error:
traceback.print_exc()
Which prints:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Better than printing, logging:
However, a best practice is to have a logger set up for your module. It will know the name of the module and be able to change levels (among other attributes, such as handlers)
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
In which case, you'll want the logger.exception function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.exception(error)
Which logs:
ERROR:__main__:something bad happened!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Or perhaps you just want the string, in which case, you'll want the traceback.format_exc function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.debug(traceback.format_exc())
Which logs:
DEBUG:__main__:Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Conclusion
And for all three options, we see we get the same output as when we have an error:
>>> do_something_that_might_error()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Which to use
Performance concerns aren't important here as IO usually dominates. I'd prefer, since it does precisely what's being requested in a forward compatible way:
logger.exception(error)
Logging levels and outputs can be adjusted, making it easy to turn off without touching the code. And usually doing what's directly needed is the most efficient way to do it.
First, do not use prints for logging, there is a stable, proven and well-thought out stdlib module to do that: logging. You definitely should use it instead.
Second, do not be tempted to do a mess with unrelated tools when there is a native and simple approach. Here it is:
log = logging.getLogger(__name__)
try:
call_code_that_fails()
except MyError:
log.exception('Any extra info you want to see in your logs')
That's it. You are done now.
Explanation for anyone who is interested in how things work under the hood
What log.exception is actually doing is just a call to log.error (that is, log event with level ERROR) and print traceback then.
Why is it better?
Well, here are some considerations:
it is just right;
it is straightforward;
it is simple.
Why should nobody use traceback or call logger with exc_info=True or get their hands dirty with sys.exc_info?
Well, just because! They all exist for different purposes. For example, traceback.print_exc's output is a little bit different from tracebacks produced by the interpreter itself. If you use it, you will confuse anyone who reads your logs, they will be banging their heads against them.
Passing exc_info=True to log calls is just inappropriate. But, it is useful when catching recoverable errors and you want to log them (using, e.g INFO level) with tracebacks as well, because log.exception produces logs of only one level - ERROR.
And you definitely should avoid messing with sys.exc_info as much as you can. It's just not a public interface, it's an internal one - you can use it if you definitely know what you are doing. It is not intended for just printing exceptions.
traceback.format_exception(exception_object)
If you only have the exception object, you can get the traceback as a string from any point of the code in Python 3 with:
import traceback
''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
Full example:
#!/usr/bin/env python3
import traceback
def f():
g()
def g():
raise Exception('asdf')
try:
g()
except Exception as e:
exc_obj = e
tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)
Output:
Traceback (most recent call last):
File "./main.py", line 12, in <module>
g()
File "./main.py", line 9, in g
raise Exception('asdf')
Exception: asdf
Documentation: https://docs.python.org/3.9/library/traceback.html#traceback.format_exception
See also: Extract traceback info from an exception object
Tested in Python 3.9
In addition to Aaron Hall's answer, if you are logging, but don't want to use logging.exception() (since it logs at the ERROR level), you can use a lower level and pass exc_info=True. e.g.
try:
do_something_that_might_error()
except Exception:
logging.info('General exception noted.', exc_info=True)
I don't see this mentioned in any of the other answers. If you're passing around an Exception object for whatever reason...
In Python 3.5+ you can get a trace from an Exception object using traceback.TracebackException.from_exception(). For example:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
try:
stack_lvl_3()
except Exception as e:
# raise
return e
def stack_lvl_1():
e = stack_lvl_2()
return e
e = stack_lvl_1()
tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))
However, the above code results in:
Traceback (most recent call last):
File "exc.py", line 10, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')
This is just two levels of the stack, as opposed to what would have been printed on screen had the exception been raised in stack_lvl_2() and not intercepted (uncomment the # raise line).
As I understand it, that's because an exception records only the current level of the stack when it is raised, stack_lvl_3() in this case. As it's passed back up through the stack, more levels are being added to its __traceback__. But we intercepted it in stack_lvl_2(), meaning all it got to record was levels 3 and 2. To get the full trace as printed on stdout we'd have to catch it at the highest (lowest?) level:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
stack_lvl_3()
def stack_lvl_1():
stack_lvl_2()
try:
stack_lvl_1()
except Exception as exc:
tb = traceback.TracebackException.from_exception(exc)
print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))
Which results in:
Handled at stack lvl 0
File "exc.py", line 17, in <module>
stack_lvl_1()
File "exc.py", line 13, in stack_lvl_1
stack_lvl_2()
File "exc.py", line 9, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Notice that the stack print is different, the first and last lines are missing. Because it's a different format().
Intercepting the exception as far away from the point where it was raised as possible makes for simpler code while also giving more information.
In python3 (works in 3.9) we can define a function and can use that where ever we want to print the details.
import traceback
def get_traceback(e):
lines = traceback.format_exception(type(e), e, e.__traceback__)
return ''.join(lines)
try:
1/0
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
try:
spam(1,2)
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
The output would be like:
bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
1/0
ZeroDivisionError: division by zero
------End--------
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
spam(1,2)
NameError: name 'spam' is not defined
------End--------
If you have an Error object already, and you want to print the whole thing, you need to make this slightly awkward call:
import traceback
traceback.print_exception(type(err), err, err.__traceback__)
That's right, print_exception takes three positional arguments: The type of the exception, the actual exception object, and the exception's own internal traceback property.
In python 3.5 or later, the type(err) is optional... but it's a positional argument, so you still have to explicitly pass None in its place.
traceback.print_exception(None, err, err.__traceback__)
I have no idea why all of this isn't just traceback.print_exception(err). Why you would ever want to print out an error, along with a traceback other than the one that belongs to that error, is beyond me.
To get the precise stack trace, as a string, that would have been raised if no try/except were there to step over it, simply place this in the except block that catches the offending exception.
desired_trace = traceback.format_exc(sys.exc_info())
Here's how to use it (assuming flaky_func is defined, and log calls your favorite logging system):
import traceback
import sys
try:
flaky_func()
except KeyboardInterrupt:
raise
except Exception:
desired_trace = traceback.format_exc(sys.exc_info())
log(desired_trace)
It's a good idea to catch and re-raise KeyboardInterrupts, so that you can still kill the program using Ctrl-C. Logging is outside the scope of the question, but a good option is logging. Documentation for the sys and traceback modules.
You will need to put the try/except inside the most innerloop where the error may occur, i.e.
for i in something:
for j in somethingelse:
for k in whatever:
try:
something_complex(i, j, k)
except Exception, e:
print e
try:
something_less_complex(i, j)
except Exception, e:
print e
... and so on
In other words, you will need to wrap statements that may fail in try/except as specific as possible, in the most inner-loop as possible.
A remark about this answer's comments: print(traceback.format_exc()) does a better job for me than traceback.print_exc(). With the latter, the hello is sometimes strangely "mixed" with the traceback text, like if both want to write to stdout or stderr at the same time, producing weird output (at least when building from inside a text editor and viewing the output in the "Build results" panel).
Traceback (most recent call last):
File "C:\Users\User\Desktop\test.py", line 7, in
hell do_stuff()
File "C:\Users\User\Desktop\test.py", line 4, in do_stuff
1/0
ZeroDivisionError: integer division or modulo by zero
o
[Finished in 0.1s]
So I use:
import traceback, sys
def do_stuff():
1/0
try:
do_stuff()
except Exception:
print(traceback.format_exc())
print('hello')
import io
import traceback
try:
call_code_that_fails()
except:
errors = io.StringIO()
traceback.print_exc(file=errors) # Instead of printing directly to stdout, the result can be further processed
contents = str(errors.getvalue())
print(contents)
errors.close()
You want the traceback module. It will let you print stack dumps like Python normally does. In particular, the print_last function will print the last exception and a stack trace.
python 3 solution
stacktrace_helper.py:
from linecache import getline
import sys
import traceback
def get_stack_trace():
exc_type, exc_value, exc_tb = sys.exc_info()
trace = traceback.format_stack()
trace = list(filter(lambda x: ("\\lib\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))
ex_type = exc_type.__name__
ex_line = exc_tb.tb_lineno
ex_file = exc_tb.tb_frame.f_code.co_filename
ex_message = str(exc_value)
line_code = ""
try:
line_code = getline(ex_file, ex_line).strip()
except:
pass
trace.insert(
0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',
)
return trace
def get_stack_trace_str(msg: str = ""):
trace = list(get_stack_trace())
trace_str = "\n".join(list(map(str, trace)))
trace_str = msg + "\n" + trace_str
return trace_str
This is my solution to write the error in a log file and also on console:
import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
exc_info=(exc_type, exc_value, exc_traceback)
logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
print("An error occured, check error.log to see the error details")
traceback.print_exception(*exc_info)
sys.excepthook = handle_exception
You could do:
try:
do_stuff()
except Exception, err:
print(Exception, err)
raise err

How to print arguments of all types of Exceptions [duplicate]

I want to catch and log exceptions without exiting, e.g.,
try:
do_stuff()
except Exception as err:
print(Exception, err)
# I want to print the entire traceback here,
# not just the exception name and details
I want to print the exact same output that is printed when the exception is raised without the try/except intercepting the exception, and I do not want it to exit my program.
traceback.format_exc() or sys.exc_info() will yield more info if that's what you want.
import traceback
import sys
try:
do_stuff()
except Exception:
print(traceback.format_exc())
# or
print(sys.exc_info()[2])
Some other answer have already pointed out the traceback module.
Please notice that with print_exc, in some corner cases, you will not obtain what you would expect. In Python 2.x:
import traceback
try:
raise TypeError("Oups!")
except Exception, err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_exc()
...will display the traceback of the last exception:
Traceback (most recent call last):
File "e.py", line 7, in <module>
raise TypeError("Again !?!")
TypeError: Again !?!
If you really need to access the original traceback one solution is to cache the exception infos as returned from exc_info in a local variable and display it using print_exception:
import traceback
import sys
try:
raise TypeError("Oups!")
except Exception, err:
try:
exc_info = sys.exc_info()
# do you usefull stuff here
# (potentially raising an exception)
try:
raise TypeError("Again !?!")
except:
pass
# end of useful stuff
finally:
# Display the *original* exception
traceback.print_exception(*exc_info)
del exc_info
Producing:
Traceback (most recent call last):
File "t.py", line 6, in <module>
raise TypeError("Oups!")
TypeError: Oups!
Few pitfalls with this though:
From the doc of sys_info:
Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement)
but, from the same doc:
Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.
On the other hand, by allowing you to access the traceback associated with an exception, Python 3 produce a less surprising result:
import traceback
try:
raise TypeError("Oups!")
except Exception as err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_tb(err.__traceback__)
... will display:
File "e3.py", line 4, in <module>
raise TypeError("Oups!")
If you're debugging and just want to see the current stack trace, you can simply call:
traceback.print_stack()
There's no need to manually raise an exception just to catch it again.
How to print the full traceback without halting the program?
When you don't want to halt your program on an error, you need to handle that error with a try/except:
try:
do_something_that_might_error()
except Exception as error:
handle_the_error(error)
To extract the full traceback, we'll use the traceback module from the standard library:
import traceback
And to create a decently complicated stacktrace to demonstrate that we get the full stacktrace:
def raise_error():
raise RuntimeError('something bad happened!')
def do_something_that_might_error():
raise_error()
Printing
To print the full traceback, use the traceback.print_exc method:
try:
do_something_that_might_error()
except Exception as error:
traceback.print_exc()
Which prints:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Better than printing, logging:
However, a best practice is to have a logger set up for your module. It will know the name of the module and be able to change levels (among other attributes, such as handlers)
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
In which case, you'll want the logger.exception function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.exception(error)
Which logs:
ERROR:__main__:something bad happened!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Or perhaps you just want the string, in which case, you'll want the traceback.format_exc function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.debug(traceback.format_exc())
Which logs:
DEBUG:__main__:Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Conclusion
And for all three options, we see we get the same output as when we have an error:
>>> do_something_that_might_error()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Which to use
Performance concerns aren't important here as IO usually dominates. I'd prefer, since it does precisely what's being requested in a forward compatible way:
logger.exception(error)
Logging levels and outputs can be adjusted, making it easy to turn off without touching the code. And usually doing what's directly needed is the most efficient way to do it.
First, do not use prints for logging, there is a stable, proven and well-thought out stdlib module to do that: logging. You definitely should use it instead.
Second, do not be tempted to do a mess with unrelated tools when there is a native and simple approach. Here it is:
log = logging.getLogger(__name__)
try:
call_code_that_fails()
except MyError:
log.exception('Any extra info you want to see in your logs')
That's it. You are done now.
Explanation for anyone who is interested in how things work under the hood
What log.exception is actually doing is just a call to log.error (that is, log event with level ERROR) and print traceback then.
Why is it better?
Well, here are some considerations:
it is just right;
it is straightforward;
it is simple.
Why should nobody use traceback or call logger with exc_info=True or get their hands dirty with sys.exc_info?
Well, just because! They all exist for different purposes. For example, traceback.print_exc's output is a little bit different from tracebacks produced by the interpreter itself. If you use it, you will confuse anyone who reads your logs, they will be banging their heads against them.
Passing exc_info=True to log calls is just inappropriate. But, it is useful when catching recoverable errors and you want to log them (using, e.g INFO level) with tracebacks as well, because log.exception produces logs of only one level - ERROR.
And you definitely should avoid messing with sys.exc_info as much as you can. It's just not a public interface, it's an internal one - you can use it if you definitely know what you are doing. It is not intended for just printing exceptions.
traceback.format_exception(exception_object)
If you only have the exception object, you can get the traceback as a string from any point of the code in Python 3 with:
import traceback
''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
Full example:
#!/usr/bin/env python3
import traceback
def f():
g()
def g():
raise Exception('asdf')
try:
g()
except Exception as e:
exc_obj = e
tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)
Output:
Traceback (most recent call last):
File "./main.py", line 12, in <module>
g()
File "./main.py", line 9, in g
raise Exception('asdf')
Exception: asdf
Documentation: https://docs.python.org/3.9/library/traceback.html#traceback.format_exception
See also: Extract traceback info from an exception object
Tested in Python 3.9
In addition to Aaron Hall's answer, if you are logging, but don't want to use logging.exception() (since it logs at the ERROR level), you can use a lower level and pass exc_info=True. e.g.
try:
do_something_that_might_error()
except Exception:
logging.info('General exception noted.', exc_info=True)
I don't see this mentioned in any of the other answers. If you're passing around an Exception object for whatever reason...
In Python 3.5+ you can get a trace from an Exception object using traceback.TracebackException.from_exception(). For example:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
try:
stack_lvl_3()
except Exception as e:
# raise
return e
def stack_lvl_1():
e = stack_lvl_2()
return e
e = stack_lvl_1()
tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))
However, the above code results in:
Traceback (most recent call last):
File "exc.py", line 10, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')
This is just two levels of the stack, as opposed to what would have been printed on screen had the exception been raised in stack_lvl_2() and not intercepted (uncomment the # raise line).
As I understand it, that's because an exception records only the current level of the stack when it is raised, stack_lvl_3() in this case. As it's passed back up through the stack, more levels are being added to its __traceback__. But we intercepted it in stack_lvl_2(), meaning all it got to record was levels 3 and 2. To get the full trace as printed on stdout we'd have to catch it at the highest (lowest?) level:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
stack_lvl_3()
def stack_lvl_1():
stack_lvl_2()
try:
stack_lvl_1()
except Exception as exc:
tb = traceback.TracebackException.from_exception(exc)
print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))
Which results in:
Handled at stack lvl 0
File "exc.py", line 17, in <module>
stack_lvl_1()
File "exc.py", line 13, in stack_lvl_1
stack_lvl_2()
File "exc.py", line 9, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Notice that the stack print is different, the first and last lines are missing. Because it's a different format().
Intercepting the exception as far away from the point where it was raised as possible makes for simpler code while also giving more information.
In python3 (works in 3.9) we can define a function and can use that where ever we want to print the details.
import traceback
def get_traceback(e):
lines = traceback.format_exception(type(e), e, e.__traceback__)
return ''.join(lines)
try:
1/0
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
try:
spam(1,2)
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
The output would be like:
bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
1/0
ZeroDivisionError: division by zero
------End--------
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
spam(1,2)
NameError: name 'spam' is not defined
------End--------
If you have an Error object already, and you want to print the whole thing, you need to make this slightly awkward call:
import traceback
traceback.print_exception(type(err), err, err.__traceback__)
That's right, print_exception takes three positional arguments: The type of the exception, the actual exception object, and the exception's own internal traceback property.
In python 3.5 or later, the type(err) is optional... but it's a positional argument, so you still have to explicitly pass None in its place.
traceback.print_exception(None, err, err.__traceback__)
I have no idea why all of this isn't just traceback.print_exception(err). Why you would ever want to print out an error, along with a traceback other than the one that belongs to that error, is beyond me.
To get the precise stack trace, as a string, that would have been raised if no try/except were there to step over it, simply place this in the except block that catches the offending exception.
desired_trace = traceback.format_exc(sys.exc_info())
Here's how to use it (assuming flaky_func is defined, and log calls your favorite logging system):
import traceback
import sys
try:
flaky_func()
except KeyboardInterrupt:
raise
except Exception:
desired_trace = traceback.format_exc(sys.exc_info())
log(desired_trace)
It's a good idea to catch and re-raise KeyboardInterrupts, so that you can still kill the program using Ctrl-C. Logging is outside the scope of the question, but a good option is logging. Documentation for the sys and traceback modules.
You will need to put the try/except inside the most innerloop where the error may occur, i.e.
for i in something:
for j in somethingelse:
for k in whatever:
try:
something_complex(i, j, k)
except Exception, e:
print e
try:
something_less_complex(i, j)
except Exception, e:
print e
... and so on
In other words, you will need to wrap statements that may fail in try/except as specific as possible, in the most inner-loop as possible.
A remark about this answer's comments: print(traceback.format_exc()) does a better job for me than traceback.print_exc(). With the latter, the hello is sometimes strangely "mixed" with the traceback text, like if both want to write to stdout or stderr at the same time, producing weird output (at least when building from inside a text editor and viewing the output in the "Build results" panel).
Traceback (most recent call last):
File "C:\Users\User\Desktop\test.py", line 7, in
hell do_stuff()
File "C:\Users\User\Desktop\test.py", line 4, in do_stuff
1/0
ZeroDivisionError: integer division or modulo by zero
o
[Finished in 0.1s]
So I use:
import traceback, sys
def do_stuff():
1/0
try:
do_stuff()
except Exception:
print(traceback.format_exc())
print('hello')
import io
import traceback
try:
call_code_that_fails()
except:
errors = io.StringIO()
traceback.print_exc(file=errors) # Instead of printing directly to stdout, the result can be further processed
contents = str(errors.getvalue())
print(contents)
errors.close()
You want the traceback module. It will let you print stack dumps like Python normally does. In particular, the print_last function will print the last exception and a stack trace.
python 3 solution
stacktrace_helper.py:
from linecache import getline
import sys
import traceback
def get_stack_trace():
exc_type, exc_value, exc_tb = sys.exc_info()
trace = traceback.format_stack()
trace = list(filter(lambda x: ("\\lib\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))
ex_type = exc_type.__name__
ex_line = exc_tb.tb_lineno
ex_file = exc_tb.tb_frame.f_code.co_filename
ex_message = str(exc_value)
line_code = ""
try:
line_code = getline(ex_file, ex_line).strip()
except:
pass
trace.insert(
0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',
)
return trace
def get_stack_trace_str(msg: str = ""):
trace = list(get_stack_trace())
trace_str = "\n".join(list(map(str, trace)))
trace_str = msg + "\n" + trace_str
return trace_str
This is my solution to write the error in a log file and also on console:
import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
exc_info=(exc_type, exc_value, exc_traceback)
logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
print("An error occured, check error.log to see the error details")
traceback.print_exception(*exc_info)
sys.excepthook = handle_exception
You could do:
try:
do_stuff()
except Exception, err:
print(Exception, err)
raise err

Propagate exceptions in Python 2 conserving the backtrace [duplicate]

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
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
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]
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
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)
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.
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)

Adding information to an exception?

This question already has answers here:
How do I raise the same Exception with a custom message in Python?
(17 answers)
Why doesn't it work to append information in the exception message?
(2 answers)
Closed 2 days ago.
I want to achieve something like this:
def foo():
try:
raise IOError('Stuff ')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
e.message = e.message + 'happens at %s' % arg1
raise
bar('arg1')
Traceback...
IOError('Stuff Happens at arg1')
But what I get is:
Traceback..
IOError('Stuff')
Any clues as to how to achieve this? How to do it both in Python 2 and 3?
In case you came here searching for a solution for Python 3 the manual says:
When raising a new exception (rather than using a bare raise to re-raise the exception currently being handled), the implicit exception context can be supplemented with an explicit cause by using from with raise:
raise new_exc from original_exc
Example:
try:
return [permission() for permission in self.permission_classes]
except TypeError as e:
raise TypeError("Make sure your view's 'permission_classes' are iterable. "
"If you use '()' to generate a set with a single element "
"make sure that there is a comma behind the one (element,).") from e
Which looks like this in the end:
2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
# Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If
you use parens () to generate a set with a single element make
sure that there is a (comma,) behind the one element.
Turning a totally nondescript TypeError into a nice message with hints towards a solution without messing up the original Exception.
I'd do it like this so changing its type in foo() won't require also changing it in bar().
def foo():
try:
raise IOError('Stuff')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
raise type(e)(e.message + ' happens at %s' % arg1)
bar('arg1')
Traceback (most recent call last):
File "test.py", line 13, in <module>
bar('arg1')
File "test.py", line 11, in bar
raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1
Update 1
Here's a slight modification that preserves the original traceback:
...
def bar(arg1):
try:
foo()
except Exception as e:
import sys
raise type(e), type(e)(e.message +
' happens at %s' % arg1), sys.exc_info()[2]
bar('arg1')
Traceback (most recent call last):
File "test.py", line 16, in <module>
bar('arg1')
File "test.py", line 11, in bar
foo()
File "test.py", line 5, in foo
raise IOError('Stuff')
IOError: Stuff happens at arg1
Update 2
For Python 3.x, the code in my first update is syntactically incorrect plus the idea of having a message attribute on BaseException was retracted in a change to PEP 352 on 2012-05-16 (my first update was posted on 2012-03-12). So currently, in Python 3.5.2 anyway, you'd need to do something along these lines to preserve the traceback and not hardcode the type of exception in function bar(). Also note that there will be the line:
During handling of the above exception, another exception occurred:
in the traceback messages displayed.
# for Python 3.x
...
def bar(arg1):
try:
foo()
except Exception as e:
import sys
raise type(e)(str(e) +
' happens at %s' % arg1).with_traceback(sys.exc_info()[2])
bar('arg1')
Update 3
A commenter asked if there was a way that would work in both Python 2 and 3. Although the answer might seem to be "No" due to the syntax differences, there is a way around that by using a helper function like reraise() in the six add-on module. So, if you'd rather not use the library for some reason, below is a simplified standalone version.
Note too, that since the exception is reraised within the reraise() function, that will appear in whatever traceback is raised, but the final result is what you want.
import sys
if sys.version_info.major < 3: # Python 2?
# Using exec avoids a SyntaxError in Python 3.
exec("""def reraise(exc_type, exc_value, exc_traceback=None):
raise exc_type, exc_value, exc_traceback""")
else:
def reraise(exc_type, exc_value, exc_traceback=None):
if exc_value is None:
exc_value = exc_type()
if exc_value.__traceback__ is not exc_traceback:
raise exc_value.with_traceback(exc_traceback)
raise exc_value
def foo():
try:
raise IOError('Stuff')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
reraise(type(e), type(e)(str(e) +
' happens at %s' % arg1), sys.exc_info()[2])
bar('arg1')
Assuming you don't want to or can't modify foo(), you can do this:
try:
raise IOError('stuff')
except Exception as e:
if len(e.args) >= 1:
e.args = (e.args[0] + ' happens',) + e.args[1:]
raise
This is indeed the only solution here that solves the problem in Python 3 without an ugly and confusing "During handling of the above exception, another exception occurred" message.
In case the re-raising line should be added to the stack trace, writing raise e instead of raise will do the trick.
I don't like all the given answers so far. They are still too verbose imho. In either code and message output.
All i want to have is the stacktrace pointing to the source exception, no exception stuff in between, so no creation of new exceptions, just re-raising the original with all the relevant stack frame states in it, that led there.
Steve Howard gave a nice answer which i want to extend, no, reduce ... to python 3 only.
except Exception as e:
e.args = ("Some failure state", *e.args)
raise
The only new thing is the parameter expansion/unpacking which makes it small and easy enough for me to use.
Try it:
foo = None
try:
try:
state = "bar"
foo.append(state)
except Exception as e:
e.args = ("Appending '"+state+"' failed", *e.args)
raise
print(foo[0]) # would raise too
except Exception as e:
e.args = ("print(foo) failed: " + str(foo), *e.args)
raise
This will give you:
Traceback (most recent call last):
File "test.py", line 6, in <module>
foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")
A simple pretty-print could be something like
print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))
With PEP 678 (Python 3.11) adding notes to exceptions is natively supported:
try:
raise TypeError('bad type')
except Exception as e:
e.add_note('Add some information')
raise
Rendered as:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: bad type
Add some information
I was hopping it could replace Steve Howard solution, Unfortunately, it does not give user any control on how to format the final exception (e.g. can't add a note before the exception like: 'Error in fn: {original_exc}')
If you want more control on the traceback, you can use https://github.com/google/etils:
from etils import epy
with epy.maybe_reraise('Error in fn: '):
fn()
Or:
try:
fn()
except Exception as e:
epy.reraise(e, suffix='. Did you mean y ?')
One handy approach that I used is to use class attribute as storage for details, as class attribute is accessible both from class object and class instance:
class CustomError(Exception):
def __init__(self, details: Dict):
self.details = details
Then in your code:
raise CustomError({'data': 5})
And when catching an error:
except CustomError as e:
# Do whatever you want with the exception instance
print(e.details)
I will provide a snippet of code that I use often whenever I want to add extra info to an exception. I works both in Python 2.7 and 3.6.
import sys
import traceback
try:
a = 1
b = 1j
# The line below raises an exception because
# we cannot compare int to complex.
m = max(a, b)
except Exception as ex:
# I create my informational message for debugging:
msg = "a=%r, b=%r" % (a, b)
# Gather the information from the original exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Format the original exception for a nice printout:
traceback_string = ''.join(traceback.format_exception(
exc_type, exc_value, exc_traceback))
# Re-raise a new exception of the same class as the original one,
# using my custom message and the original traceback:
raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))
The code above results in the following output:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
14 raise type(ex)(
15 "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16 (msg, traceback_string))
TypeError: a=1, b=1j
ORIGINAL TRACEBACK:
Traceback (most recent call last):
File "<ipython-input-6-09b74752c60d>", line 7, in <module>
m = max(a, b) # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers
I know this deviates a little from the example provided in the question, but nevertheless I hope someone finds it useful.
You can define your own exception that inherits from another and create it's own constructor to set value.
For example:
class MyError(Exception):
def __init__(self, value):
self.value = value
Exception.__init__(self)
def __str__(self):
return repr(self.value)
Unlike previous answers, this works in the face of exceptions with really bad __str__.
It does modify the type however, in order to factor out unhelpful __str__ implementations.
I'd still like to find an additional improvement that doesn't modify the type.
from contextlib import contextmanager
#contextmanager
def helpful_info():
try:
yield
except Exception as e:
class CloneException(Exception): pass
CloneException.__name__ = type(e).__name__
CloneException.__module___ = type(e).__module__
helpful_message = '%s\n\nhelpful info!' % e
import sys
raise CloneException, helpful_message, sys.exc_traceback
class BadException(Exception):
def __str__(self):
return 'wat.'
with helpful_info():
raise BadException('fooooo')
The original traceback and type (name) are preserved.
Traceback (most recent call last):
File "re_raise.py", line 20, in <module>
raise BadException('fooooo')
File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
self.gen.throw(type, value, traceback)
File "re_raise.py", line 5, in helpful_info
yield
File "re_raise.py", line 20, in <module>
raise BadException('fooooo')
__main__.BadException: wat.
helpful info!
Here's what I use for personal projects (I'm sure there's ample reason not to do this in production code):
try:
#something hazardous
except Exception as e:
insightful_message = "shouldn't have done that"
amended_args = tuple([f'{e.args[0]}\n{insightful_message}', *e.args[1:]])
e.args = amended_args
raise
The code (1) intercepts the error; (2) creates a copy of the error's .args property, which is a tuple that is assumed to include an error message at index 0, achieved using a list comprehension; (3) appends a line break and a custom message to the error message; (4) appends any additional items of .args to the copy using
unpacking; (5) converts the copy to a tuple; and finally (6) replaces .args with the amended copy.
Most of these operations are to circumvent the immutability of the .args tuple.
This is my implementation, to use it as a context manager and optionally add extra message to exception:
from typing import Optional, Type
from types import TracebackType
class _addInfoOnException():
def __init__(self, info: str = ""):
self.info = info
def __enter__(self):
return
def __exit__(self,
exc_type: Optional[Type[BaseException]],
exc_val: BaseException, # Optional, but not None if exc_type is not None
exc_tb: TracebackType): # Optional, but not None if exc_type is not None
if exc_type:
if self.info:
newMsg = f"{self.info}\n\tLow level error: "
if len(exc_val.args) == 0:
exc_val.args = (self.info, )
elif len(exc_val.args) == 1:
exc_val.args = (f"{newMsg}{exc_val.args[0]}", )
elif len(exc_val.args) > 0:
exc_val.args = (f"{newMsg}{exc_val.args[0]}", exc_val.args[1:])
raise
Usage:
def main():
try:
raise Exception("Example exception msg")
except Exception:
traceback.print_exc()
print("\n\n")
try:
with _addInfoOnException():
raise Exception("Example exception msg, no extra info")
except Exception:
traceback.print_exc()
print("\n\n")
try:
with _addInfoOnException("Some extra info!"):
raise Exception("Example exception msg")
except Exception:
traceback.print_exc()
print("\n\n")
if __name__ == "__main__":
main()
This would resolve in such traceback:
Traceback (most recent call last):
File "<...>\VSCodeDevWorkspace\testis.py", line 40, in main
raise Exception("Example exception msg")
Exception: Example exception msg
Traceback (most recent call last):
File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
raise Exception("Example exception msg, no extra info")
File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
raise Exception("Example exception msg, no extra info")
Exception: Example exception msg, no extra info
Traceback (most recent call last):
File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
raise Exception("Example exception msg")
File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
raise Exception("Example exception msg")
Exception: Some extra info!
Low level error: Example exception msg
I use in my codes:
try:
a=1
b=0
c=a/b
except:
raise Exception(f"can't divide {a} with {b}")
output:
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11708/1469673756.py in <module>
3 b=0
----> 4 c=a/b
5
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Exception Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11708/1469673756.py in <module>
5
6 except Exception:
----> 7 raise Exception(f"can't divide {a} with {b}")
Exception: can't divide 1 with 0
Maybe
except Exception as e:
raise IOError(e.message + 'happens at %s'%arg1)

Categories

Resources