I've seen a lot of posts about stack trace and exceptions in Python. But haven't found what I need.
I have a chunk of Python 2.7 code that may raise an exception. I would like to catch it and assign to a string its full description and the stack trace that caused the error (simply all we use to see on the console). I need this string to print it to a text box in the GUI.
Something like this:
try:
method_that_can_raise_an_exception(params)
except Exception as e:
print_to_textbox(complete_exception_description(e))
The problem is: what is the function complete_exception_description
?
See the traceback
module, specifically the format_exc()
function. Here.
import traceback
try:
raise ValueError
except ValueError:
tb = traceback.format_exc()
else:
tb = "No error"
finally:
print tb
Let's create a decently complicated stacktrace, in order to demonstrate that we get the full stacktrace:
def raise_error():
raise RuntimeError('something bad happened!')
def do_something_that_might_error():
raise_error()
Logging the full stacktrace
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__)
And we can use this logger to get the error:
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!
And so 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!
Getting just the string
If you really just want the string, use the traceback.format_exc
function instead, demonstrating logging the string here:
import traceback
try:
do_something_that_might_error()
except Exception as error:
just_the_string = traceback.format_exc()
logger.debug(just_the_string)
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!
except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
exc_info
argument expects an "exception tuple" whereas the error
is an instance of the Exception
object (or subclass), and there is no need to change error
to e
.
With Python 3, the following code will format an Exception
object exactly as would be obtained using traceback.format_exc()
:
import traceback
try:
method_that_can_raise_an_exception(params)
except Exception as ex:
print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))
The advantage being that only the Exception
object is needed (thanks to the recorded __traceback__
attribute), and can therefore be more easily passed as an argument to another function for further processing.
.__traceback__
and type
, see stackoverflow.com/a/58764987/5717886
>>> import sys
>>> import traceback
>>> try:
... 5 / 0
... except ZeroDivisionError as e:
... type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
[' File "<stdin>", line 2, in <module>\n']
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
You use sys.exc_info() to collect the information and the functions in the traceback
module to format it. Here are some examples for formatting it.
The whole exception string is at:
>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):\n', ' File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: integer division or modulo by zero\n']
For Python 3.5+:
So, you can get the stacktrace from your exception as from any other exception. Use traceback.TracebackException
for it (just replace ex
with your exception):
print("".join(traceback.TracebackException.from_exception(ex).format())
An extended example and other features to do this:
import traceback
try:
1/0
except Exception as ex:
print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
print("".join(traceback.TracebackException.from_exception(ex).format()))
The output will be something like this:
True
Traceback (most recent call last):
File "untidsfsdfsdftled.py", line 29, in <module>
1/0
ZeroDivisionError: division by zero
For those using Python-3
Using traceback
module and exception.__traceback__
one can extract the stack-trace as follows:
grab the current stack-trace using traceback.extract_stack()
remove the last three elements (as those are entries in the stack that got me to my debug function)
append the __traceback__ from the exception object using traceback.extract_tb()
format the whole thing using traceback.format_list()
import traceback
def exception_to_string(excp):
stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__) # add limit=??
pretty = traceback.format_list(stack)
return ''.join(pretty) + '\n {} {}'.format(excp.__class__,excp)
A simple demonstration:
def foo():
try:
something_invalid()
except Exception as e:
print(exception_to_string(e))
def bar():
return foo()
We get the following output when we call bar()
:
File "./test.py", line 57, in <module>
bar()
File "./test.py", line 55, in bar
return foo()
File "./test.py", line 50, in foo
something_invalid()
<class 'NameError'> name 'something_invalid' is not defined
You might also consider using the built-in Python module, cgitb, to get some really good, nicely formatted exception information including local variable values, source code context, function parameters etc..
For instance for this code...
import cgitb
cgitb.enable(format='text')
def func2(a, divisor):
return a / divisor
def func1(a, b):
c = b - 5
return func2(a, c)
func1(1, 5)
we get this exception output...
ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.
c:\TEMP\cgittest2.py in <module>()
7 def func1(a, b):
8 c = b - 5
9 return func2(a, c)
10
11 func1(1, 5)
func1 = <function func1>
c:\TEMP\cgittest2.py in func1(a=1, b=5)
7 def func1(a, b):
8 c = b - 5
9 return func2(a, c)
10
11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0
c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
3
4 def func2(a, divisor):
5 return a / divisor
6
7 def func1(a, b):
a = 1
divisor = 0
ZeroDivisionError: division by zero
__cause__ = None
__class__ = <class 'ZeroDivisionError'>
__context__ = None
__delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object>
__dict__ = {}
__dir__ = <built-in method __dir__ of ZeroDivisionError object>
__doc__ = 'Second argument to a division or modulo operation was zero.'
__eq__ = <method-wrapper '__eq__' of ZeroDivisionError object>
__format__ = <built-in method __format__ of ZeroDivisionError object>
__ge__ = <method-wrapper '__ge__' of ZeroDivisionError object>
__getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object>
__gt__ = <method-wrapper '__gt__' of ZeroDivisionError object>
__hash__ = <method-wrapper '__hash__' of ZeroDivisionError object>
__init__ = <method-wrapper '__init__' of ZeroDivisionError object>
__le__ = <method-wrapper '__le__' of ZeroDivisionError object>
__lt__ = <method-wrapper '__lt__' of ZeroDivisionError object>
__ne__ = <method-wrapper '__ne__' of ZeroDivisionError object>
__new__ = <built-in method __new__ of type object>
__reduce__ = <built-in method __reduce__ of ZeroDivisionError object>
__reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object>
__repr__ = <method-wrapper '__repr__' of ZeroDivisionError object>
__setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object>
__setstate__ = <built-in method __setstate__ of ZeroDivisionError object>
__sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object>
__str__ = <method-wrapper '__str__' of ZeroDivisionError object>
__subclasshook__ = <built-in method __subclasshook__ of type object>
__suppress_context__ = False
__traceback__ = <traceback object>
args = ('division by zero',)
with_traceback = <built-in method with_traceback of ZeroDivisionError object>
The above is a description of an error in a Python program. Here is
the original traceback:
Traceback (most recent call last):
File "cgittest2.py", line 11, in <module>
func1(1, 5)
File "cgittest2.py", line 9, in func1
return func2(a, c)
File "cgittest2.py", line 5, in func2
return a / divisor
ZeroDivisionError: division by zero
If you would like to get the same information given when an exception isn't handled you can do something like this. Do import traceback
and then:
try:
...
except Exception as e:
print(traceback.print_tb(e.__traceback__))
I'm using Python 3.7.
If your goal is to make the exception and stacktrace message look exactly like when python throws an error, the following works in both python 2+3:
import sys, traceback
def format_stacktrace():
parts = ["Traceback (most recent call last):\n"]
parts.extend(traceback.format_stack(limit=25)[:-2])
parts.extend(traceback.format_exception(*sys.exc_info())[1:])
return "".join(parts)
# EXAMPLE BELOW...
def a():
b()
def b():
c()
def c():
d()
def d():
assert False, "Noooh don't do it."
print("THIS IS THE FORMATTED STRING")
print("============================\n")
try:
a()
except:
stacktrace = format_stacktrace()
print(stacktrace)
print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()
It works by removing the last format_stacktrace()
call from the stack and joining the rest. When run, the example above gives the following output:
THIS IS THE FORMATTED STRING
============================
Traceback (most recent call last):
File "test.py", line 31, in <module>
a()
File "test.py", line 12, in a
b()
File "test.py", line 16, in b
c()
File "test.py", line 20, in c
d()
File "test.py", line 24, in d
assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.
THIS IS HOW PYTHON DOES IT
==========================
Traceback (most recent call last):
File "test.py", line 38, in <module>
a()
File "test.py", line 12, in a
b()
File "test.py", line 16, in b
c()
File "test.py", line 20, in c
d()
File "test.py", line 24, in d
assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.
my 2-cents:
import sys, traceback
try:
...
except Exception, e:
T, V, TB = sys.exc_info()
print ''.join(traceback.format_exception(T,V,TB))
I defined following helper class:
import traceback
class TracedExeptions(object):
def __init__(self):
pass
def __enter__(self):
pass
def __exit__(self, etype, value, tb):
if value :
if not hasattr(value, 'traceString'):
value.traceString = "\n".join(traceback.format_exception(etype, value, tb))
return False
return True
Which I can later use like this:
with TracedExeptions():
#some-code-which-might-throw-any-exception
And later can consume it like this:
def log_err(ex):
if hasattr(ex, 'traceString'):
print("ERROR:{}".format(ex.traceString));
else:
print("ERROR:{}".format(ex));
(Background: I was frustraded because of using Promise
s together with Exception
s, which unfortunately passes exceptions raised in one place to a on_rejected handler in another place, and thus it is difficult to get the traceback from original location)
If you would like to convert your traceback to a list of dict (for python > 3.5):
from traceback import TracebackException
def list_traceback(exc_value: BaseException):
result = list()
# get previous fails, so errors are appended by order of execution
if exc_value.__context__:
result += list_traceback(exc_value.__context__)
# convert Exception into TracebackException
tbe = TracebackException.from_exception(exc_value)
# get stacktrace (cascade methods calls)
error_lines = list()
for frame_summary in tbe.stack:
summary_details = {
'filename': frame_summary.filename,
'method' : frame_summary.name,
'lineno' : frame_summary.lineno,
'code' : frame_summary.line
}
error_lines.append(summary_details)
# append error, by order of execution
result.append({"error_lines": error_lines,
"type" : tbe.exc_type.__name__,
"message" : str(tbe)})
return result
This will be (an example of) the result:
[
{
"error_lines": [
{
"filename": "/home/demo/file2.py",
"method": "do_error_2",
"lineno": 18,
"code": "a=1/0"
}
],
"type": "ZeroDivisionError",
"message": "division by zero"
},
{
"error_lines": [
{
"filename": "/home/demo/file_main.py",
"method": "demo2",
"lineno": 50,
"code": "file2.DEMO().do_error_2()"
},
{
"filename": "/home/demo/file2.py",
"method": "do_error_2",
"lineno": 20,
"code": "raise AssertionError(\"Raised inside the except, after division by zero\")"
}
],
"type": "AssertionError",
"message": "Raised inside the except, after division by zero"
}
]
Success story sharing
log_error(err)
function.try
can take anelse
clause.