Get exception description and stack trace which caused an exception, all as a string
Asked Answered
B

12

628

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))
Bodgie answered 30/12, 2010 at 16:59 Comment(2)
what is wrong with: sys.exc_info()?Kissiah
@CharlieParker It has no trace.Sproul
E
886

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
Epithelium answered 30/12, 2010 at 17:5 Comment(8)
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.Aborticide
It works with the error that was caught and handled.Epithelium
In addition to what I was looking for, I just learned that try can take an else clause.Meander
Can you show the output in case of an exception?Suggestible
what does tb = traceback.format_exc() do or the type it returns?Kissiah
what is wrong with: sys.exc_info()?Kissiah
That's a useless "else", it would never go there...Lambert
I mean, yes, it's useless in this toy example, but in a real situation you'd want to handle that possibility.Epithelium
C
125

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!
Cioffi answered 16/7, 2015 at 3:47 Comment(4)
@Yunti I believe this API has been consistent across Python 2 and 3.Cioffi
Formatting of this answer was discussed on meta: meta.#386977.Resist
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)Kaitlinkaitlyn
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...Cioffi
I
91

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.

Idiotism answered 1/3, 2016 at 0:57 Comment(3)
It is better than sys.exc_info() which is not good OO style and use global variable.Sollars
This asks specifically how to get the traceback from the exception object as you have done here: #11415394Carney
There is a simpler Python3 way without using .__traceback__ and type, see https://mcmap.net/q/36591/-get-exception-description-and-stack-trace-which-caused-an-exception-all-as-a-stringJessabell
J
89

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 !!
Jessabell answered 8/11, 2019 at 10:40 Comment(0)
B
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']
Blueness answered 30/12, 2010 at 17:10 Comment(0)
M
15

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
Mada answered 10/5, 2016 at 9:54 Comment(2)
It looks like an unreadable complicated code. In Python 3.5+ there is a more elegant and simple way: https://mcmap.net/q/36591/-get-exception-description-and-stack-trace-which-caused-an-exception-all-as-a-stringJessabell
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+.Cohabit
A
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
Almond answered 22/9, 2015 at 14:39 Comment(1)
Kudos for coming up with a module I did not even know exists in the standard library.Flawy
P
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.

Patsy answered 30/1, 2019 at 20:10 Comment(0)
G
9

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.
Giggle answered 19/11, 2019 at 16:29 Comment(3)
Why use format_stack(limit=25)?Felting
@Semnodime. It limits the number of traces to 25, just like native python does it.Giggle
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/HZv5EnpRfQFelting
T
4

my 2-cents:

import sys, traceback
try: 
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))
Triazine answered 7/1, 2016 at 1:46 Comment(0)
C
1

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"
   }
]
Cysticercoid answered 5/7, 2021 at 8:19 Comment(0)
M
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)

Monson answered 26/7, 2016 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.