Related
I am writing a code which might raise unknown errors which I'd like to print in order to anticipate them, but if I run the example below nothing gets printed, could someone explain to me why this is the case? Thanks!
def example():
raise ConnectionError
try:
example()
except Exception as e:
print(e)
As written in the comment, you need to pass a message when you raise an error.
You can also use the traceback library to help you create and display the full traceback, like this:
import traceback
def example():
raise ConnectionError("This is an alarm")
try:
example()
except Exception:
print(traceback.format_exc())
Result:
Traceback (most recent call last):
File "c:\Users\xx\OneDrive\BACKUP\Python\testing_free.py", line 7, in <module>
example()
File "c:\Users\xx\OneDrive\BACKUP\Python\testing_free.py", line 4, in example
raise ConnectionError("This is an alarm")
ConnectionError: This is an alarm
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
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
Is there a way in Python to raise an error that has another error as its cause?
In Java, you can create an instance of an exception with a cause such as in the following code
try {
throw new IOException();
} catch (IOException e) {
throw new RuntimeException("An exception occurred while trying to execute", e);
}
resulting in in this error message:
Exception in thread "main" java.lang.RuntimeException: An exception occurred while trying to execute
at thing.Main.main(Main.java:11)
Caused by: java.io.IOException
at thing.Main.main(Main.java:9)
Notice that the first exception (in the stack trace) is "caused by" the second.
This is, in my opinion, a great way to show an API user that a higher-level error occurred during a call, and the developer can debug it by looking at the lower-level exception which is the "cause" of the higher-level error (in this case the RuntimeException is caused by the IOException).
With the searches I've made, I haven't been able to find anything about having an error as the cause of another in Python. Can this be achieved in Python? How? And if not, what would be a Pythonic equivalent?
In Python it is achieved by a very similar structure:
try:
raise ValueError
except ValueError:
raise ValueError('second exception')
This will generate the following traceback:
Traceback (most recent call last):
File "main.py", line 2, in <module>
raise ValueError
ValueError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('second exception')
ValueError: second exception
Another Python feature is raise from which provide a slightly different traceback:
try:
raise ValueError
except ValueError as e:
raise ValueError('second exception') from e
Traceback:
Traceback (most recent call last):
File "main.py", line 2, in <module>
raise ValueError
ValueError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('second exception') from e
ValueError: second exception
Further reading:
pep-3134
this SO answer
In the module warnings (https://docs.python.org/3.5/library/warnings.html) there is the ability to raise a warning that appears to come from somewhere earlier in the stack:
warnings.warn('This is a test', stacklevel=2)
Is there an equivalent for raising errors? I know I can raise an error with an alternative traceback, but I can't create that traceback within the module since it needs to come from earlier. I imagine something like:
tb = magic_create_traceback_right_here()
raise ValueError('This is a test').with_traceback(tb.tb_next)
The reason is that I am developing a module that has a function module.check_raise that I want to raise an error that appears to originate from where the function is called. If I raise an error within the module.check_raise function, it appears to originate from within module.check_raise, which is undesired.
Also, I've tried tricks like raising a dummy exception, catching it, and passing the traceback along, but somehow the tb_next becomes None. I'm out of ideas.
Edit:
I would like the output of this minimal example (called tb2.py):
import check_raise
check_raise.raise_if_string_is_true('True')
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise.raise_if_string_is_true(string)
RuntimeError: An exception was raised.
I can't believe I am posting this
By doing this you are going against the zen.
Special cases aren't special enough to break the rules.
But if you insist here is your magical code.
check_raise.py
import sys
import traceback
def raise_if_string_is_true(string):
if string == 'true':
#the frame that called this one
f = sys._getframe().f_back
#the most USELESS error message ever
e = RuntimeError("An exception was raised.")
#the first line of an error message
print('Traceback (most recent call last):',file=sys.stderr)
#the stack information, from f and above
traceback.print_stack(f)
#the last line of the error
print(*traceback.format_exception_only(type(e),e),
file=sys.stderr, sep="",end="")
#exit the program
#if something catches this you will cause so much confusion
raise SystemExit(1)
# SystemExit is the only exception that doesn't trigger an error message by default.
This is pure python, does not interfere with sys.excepthook and even in a try block it is not caught with except Exception: although it is caught with except:
test.py
import check_raise
check_raise.raise_if_string_is_true("true")
print("this should never be printed")
will give you the (horribly uninformative and extremely forged) traceback message you desire.
Tadhgs-MacBook-Pro:Documents Tadhg$ python3 test.py
Traceback (most recent call last):
File "test.py", line 3, in <module>
check_raise.raise_if_string_is_true("true")
RuntimeError: An exception was raised.
Tadhgs-MacBook-Pro:Documents Tadhg$
If I understand correctly, you would like the output of this minimal example:
def check_raise(function):
try:
return function()
except Exception:
raise RuntimeError('An exception was raised.')
def function():
1/0
check_raise(function)
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise(function)
RuntimeError: An exception was raised.
In fact, it's a lot more output; there is exception chaining, which could be dealt with by handling the RuntimeError immediately, removing its __context__, and re-raising it, and there is another line of traceback for the RuntimeError itself:
File "tb2.py", line 5, in check_raise
raise RuntimeError('An exception was raised.')
As far as I can tell, it is not possible for pure Python code to substitute the traceback of an exception after it was raised; the interpreter has control of adding to it but it only exposes the current traceback whenever the exception is handled. There is no API (not even when using tracing functions) for passing your own traceback to the interpreter, and traceback objects are immutable (this is what's tackled by that Jinja hack involving C-level stuff).
So further assuming that you're interested in the shortened traceback not for further programmatic use but only for user-friendly output, your best bet will be an excepthook that controls how the traceback is printed to the console. For determining where to stop printing, a special local variable could be used (this is a bit more robust than limiting the traceback to its length minus 1 or such). This example requires Python 3.5 (for traceback.walk_tb):
import sys
import traceback
def check_raise(function):
__exclude_from_traceback_from_here__ = True
try:
return function()
except Exception:
raise RuntimeError('An exception was raised.')
def print_traceback(exc_type, exc_value, tb):
for i, (frame, lineno) in enumerate(traceback.walk_tb(tb)):
if '__exclude_from_traceback_from_here__' in frame.f_code.co_varnames:
limit = i
break
else:
limit = None
traceback.print_exception(
exc_type, exc_value, tb, limit=limit, chain=False)
sys.excepthook = print_traceback
def function():
1/0
check_raise(function)
This is the output now:
Traceback (most recent call last):
File "tb2.py", line 26, in <module>
check_raise(function)
RuntimeError: An exception was raised.
EDIT: The previous version did not provide quotes or explanations.
I suggest referring to PEP 3134 which states in the Motivation:
Sometimes it can be useful for an exception handler to intentionally
re-raise an exception, either to provide extra information or to
translate an exception to another type. The __cause__ attribute
provides an explicit way to record the direct cause of an exception.
When an Exception is raised with a __cause__ attribute the traceback message takes the form of:
Traceback (most recent call last):
<CAUSE TRACEBACK>
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
<MAIN TRACEBACK>
To my understanding this is exactly what you are trying to accomplish; clearly indicate that the reason for the error is not your module but somewhere else. If you are instead trying to omit information to the traceback like your edit suggests then the rest of this answer won't do you any good.
Just a note on syntax:
The __cause__ attribute on exception objects is always initialized
to None. It is set by a new form of the 'raise' statement:
raise EXCEPTION from CAUSE
which is equivalent to:
exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc
so the bare minimum example would be something like this:
def function():
int("fail")
def check_raise(function):
try:
function()
except Exception as original_error:
err = RuntimeError("An exception was raised.")
raise err from original_error
check_raise(function)
which gives an error message like this:
Traceback (most recent call last):
File "/PATH/test.py", line 7, in check_raise
function()
File "/PATH/test.py", line 3, in function
int("fail")
ValueError: invalid literal for int() with base 10: 'fail'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/PATH/test.py", line 12, in <module>
check_raise(function)
File "/PATH/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: An exception was raised.
However the first line of the cause is the statement in the try block of check_raise:
File "/PATH/test.py", line 7, in check_raise
function()
so before raising err it may (or may not) be desirable to remove the outer most traceback frame from original_error:
except Exception as original_error:
err = RuntimeError("An exception was raised.")
original_error.__traceback__ = original_error.__traceback__.tb_next
raise err from original_error
This way the only line in the traceback that appears to come from check_raise is the very last raise statement which cannot be omitted with pure python code although depending on how informative the message is you can make it very clear that your module was not the cause of the problem:
err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
The advantage to raising exception like this is that the original Traceback message is not lost when the new error is raised, which means that a very complex series of exceptions can be raised and python will still display all the relevant information correctly:
def check_raise(function):
try:
function()
except Exception as original_error:
err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
original_error.__traceback__ = original_error.__traceback__.tb_next
raise err from original_error
def test_chain():
check_raise(test)
def test():
raise ValueError
check_raise(test_chain)
gives me the following error message:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 16, in test
raise ValueError
ValueError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 13, in test_chain
check_raise(test)
File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: test encountered an error during call to __main__.check_raise
the traceback for the error is shown above.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 18, in <module>
check_raise(test_chain)
File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: test_chain encountered an error during call to __main__.check_raise
the traceback for the error is shown above.
Yes it is long but it is significantly more informative then:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 18, in <module>
check_raise(test_chain)
RuntimeError: An exception was raised.
not to mention that the original error is still usable even if the program doesn't end:
import traceback
def check_raise(function):
...
def fail():
raise ValueError
try:
check_raise(fail)
except RuntimeError as e:
cause = e.__cause__
print("check_raise failed because of this error:")
traceback.print_exception(type(cause), cause, cause.__traceback__)
print("and the program continues...")
I understand 'Don't do this'. On the other hand, there may be some special use cases i believe. I'm generating own errors (just deleting some defined frames...) this way
def get_traceback_with_removed_frames_by_line_string(lines):
"""In traceback call stack, it is possible to remove particular level defined by some line content.
Args:
lines (list): Line in call stack that we want to hide.
Returns:
string: String traceback ready to be printed.
"""
exc = trcbck.TracebackException(*sys.exc_info())
for i in exc.stack[:]:
if i.line in lines:
exc.stack.remove(i)
return "".join(exc.format())
I return just string.
If there is concrete function that is raising, you can add it to ignored frames.
Though have in mind, that if you hide something, somebody may not understand why is something happening...
My use case was to hide only first level - decorator from my library that was decorating all user functions in framework, so error from user side was on level 1.