How to convert a caught Exception (its description and stack trace) into a str for external use?
try: method_that_can_raise_an_exception(params) except Exception as e: print(complete_exception_description(e)) 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) log_error(err) function.try can take an else clause.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() 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! 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)traceback.format_exc() uses sys.exc_info(). In Python 2 it would return whatever exception was currently being handled and was buggy, sometimes returning exceptions being handled on other threads. Now the documentation currently asserts it's good, and I don't have time to look up the current state of the bug tracker...For Python 3.5+
Use traceback.TracebackException, it can handle exceptions caught anywhere.
def print_trace(ex: BaseException): print(''.join(traceback.TracebackException.from_exception(ex).format())) import traceback try: 1/0 except Exception as ex: print(''.join(traceback.TracebackException.from_exception(ex).format())) >> Output
Traceback (most recent call last): File "your_file_name_here.py", line 29, in <module> 1/0 ZeroDivisionError: division by zero It is identical to format_exc() and format_exception():
a = ''.join(traceback.TracebackException.from_exception(ex).format()) b = traceback.format_exc() c = ''.join(traceback.format_exception(type(ex), ex, ex.__traceback__)) print(a == b == c) # This is True !! 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 those using Python-3
Using traceback module and exception.__traceback__ one can extract the stack-trace as follows:
traceback.extract_stack()__traceback__ from the exception object using traceback.extract_tb()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 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. 25. The following code disproves the limit and has been evaluated at least for python 3.8 online-python.com/HZv5EnpRfQIf 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.
taceback.print_tb returns nothing, so doing traceback.pring_tb(e.__traceback__)has basically the same result. (using python 3.10) It can't be use for keeping the stacktrace on a str for external use.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" } ] 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 Promises together with Exceptions, 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)
sys.exc_info()?