652

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)) 
2
  • 1
    what is wrong with: sys.exc_info()? Commented Aug 3, 2022 at 20:00
  • @CharlieParker It has no trace. Commented Feb 8, 2023 at 16:18

12 Answers 12

912

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) 
Sign up to request clarification or add additional context in comments.

8 Comments

Does this only work with the last error? What happens if you start passing the error around to other bits of code? I'm writing a log_error(err) function.
It works with the error that was caught and handled.
In addition to what I was looking for, I just learned that try can take an else clause.
Can you show the output in case of an exception?
I mean, yes, it's useless in this toy example, but in a real situation you'd want to handle that possibility.
|
131

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! 

4 Comments

@Yunti I believe this API has been consistent across Python 2 and 3.
Formatting of this answer was discussed on meta: meta.stackoverflow.com/questions/386477/….
I sent an edit to the following but was not logged in so showing as anonymous: 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...
107

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())) 

Example

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 !! 

Comments

92

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.

3 Comments

It is better than sys.exc_info() which is not good OO style and use global variable.
This asks specifically how to get the traceback from the exception object as you have done here: stackoverflow.com/questions/11414894/…
There is a simpler Python3 way without using .__traceback__ and type, see stackoverflow.com/a/58764987/5717886
38
>>> 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'] 

Comments

16

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 

2 Comments

It looks like an unreadable complicated code. In Python 3.5+ there is a more elegant and simple way: stackoverflow.com/a/58764987/5717886
In my application this method is better to the one linked to by don_vanchos in another answer. The stack trace produced is longer and more complete, even in Python 3.5+.
10

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 

2 Comments

Kudos for coming up with a module I did not even know exists in the standard library.
Python 3.12 is the last version to support cgitb.
10

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. 

3 Comments

Why use format_stack(limit=25)?
@Semnodime. It limits the number of traces to 25, just like native python does it.
Please reference the source of limit at 25. The following code disproves the limit and has been evaluated at least for python 3.8 online-python.com/HZv5EnpRfQ
9

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.

1 Comment

The method 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.
6

my 2-cents:

import sys, traceback try: ... except Exception, e: T, V, TB = sys.exc_info() print ''.join(traceback.format_exception(T,V,TB)) 

Comments

2

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" } ] 

Comments

0

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)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.