How to catch and print the full exception traceback without halting/exiting the program?

1,060,589

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
1,060,589
chriscauley
Author by

chriscauley

Updated on July 08, 2022

Comments

  • chriscauley
    chriscauley almost 2 years

    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.

    • Vito Gentile
      Vito Gentile about 3 years
      Not a full answer, but someone might want to know that you can access lots of info looking into err.__traceback__ (at least in Python 3.x)
    • Pavel Vlasov
      Pavel Vlasov about 3 years
      People viewed it 825k times while trying to find out how to print their stacktraces. That's another Zen of Python.
    • Olivier Pons
      Olivier Pons almost 3 years
      It seems I'm the only one in the world who wants to print the stack when there's no error (= only to see how I got here at this precise line (it's not my code, and it's so ugly I cant figure out how it did come here!)).
    • Iuri Guilherme
      Iuri Guilherme about 2 years
      All the answers in this question are the ultimate beginners guide to debugging python code
  • pppery
    pppery over 8 years
    The traceback module does exactly that - raise and catch an exception.
  • herve-guerin
    herve-guerin over 6 years
    as said above and for me too, traceback.print_exc() returns only the last call : how do you succeed to return several level of the stack (and possibly all levele s?)
  • Russia Must Remove Putin
    Russia Must Remove Putin over 6 years
    @geekobi I'm not sure what you're asking here. I demonstrate that we get the traceback up to the entry point of the program/interpreter. What are you not clear on?
  • martineau
    martineau over 6 years
    This doesn't work in Python 3 and needs to be changed to desired_trace = traceback.format_exc(). Passing sys.exc_info() as the argument was never the correct thing to do, but gets silently ignored in Python 2—but not in Python 3 (3.6.4 anyway).
  • AJNeufeld
    AJNeufeld almost 6 years
    KeyboardInterrupt is not derived (directly or indirectly) from Exception. (Both are derived from BaseException.) This means except Exception: will never catch a KeyboardInterrupt, and thus the except KeyboardInterrupt: raise is completely unnecessary.
  • fizloki
    fizloki over 5 years
    What @geekobi is saying is if you catch and re-raise, traceback.print_exc() will just return the re-raise stack, not the original stack.
  • Russia Must Remove Putin
    Russia Must Remove Putin over 5 years
    @fizloki how are you "reraising"? Are you doing a bare raise or exception chaining, or are you hiding the original traceback? see stackoverflow.com/questions/2052390/…
  • mpen
    mpen over 5 years
    Output goes to STDERR by default BTW. Wasn't appearing in my logs because it was being redirected somewhere else.
  • A. Rager
    A. Rager about 5 years
    It also doesn't work as-is. That's not it. I'm not done now: this answer just wastes time.
  • Shital Shah
    Shital Shah about 5 years
    I would also add that you can just do logging.exception(). No need to create instance of log unless you have special requirements.
  • weberc2
    weberc2 over 4 years
    print(sys.exc_info()[0] prints <class 'Exception'>.
  • qrtLs
    qrtLs over 4 years
    dont use exc... the traceback contains all the info stackoverflow.com/questions/4564559/…
  • elhefe
    elhefe about 4 years
    This is a lot better than the previous method(s), but is still ridiculously convoluted just to print out a stacktrace. Java takes less code FGS.
  • Nam G VU
    Nam G VU about 4 years
    traceback.format_exc(sys.exc_info()) not working for me with python 3.6.10
  • x-yuri
    x-yuri almost 4 years
    @pppery I can't see it with python 3.8. And the thing with try and catch is that it doesn't display the full traceback, Only from raise to except.
  • x-yuri
    x-yuri almost 4 years
    It might be expected (or not), but this doesn't produce the full traceback. It's limited by the points raising the exception, and handling it (python 3.8). You don't need to try/catch to display the traceback.
  • variable
    variable almost 4 years
    In terms of performance is there any difference between the 2 approaches?
  • Russia Must Remove Putin
    Russia Must Remove Putin almost 4 years
    @variable I added a section addressing your question.
  • variable
    variable almost 4 years
    Although logger.exception is a forward compatible way, it always logs at the level of ERROR. Suppose I want to log at level of warning and I also want the full stack trace then I have 2 options: 1) logger.warning("Some warning message.", exc_info=true) OR 2) logger.warning(f"Some warning message - {traceback.format_exc()}") - Do you have any recommendation in terms of which one to choose?
  • Russia Must Remove Putin
    Russia Must Remove Putin almost 4 years
    Thanks for that followup question - I think the exc_info=True argument is actually better for logging, a keyword argument is more maintainable than custom code that puts the traceback into a string. I'll get around to updating my answer.
  • Teepeemm
    Teepeemm over 3 years
    print(sys.exc_info()[2]) yields <traceback object at 0x0000028A79E6B2C8>.
  • Break
    Break over 3 years
    I find this answer kind of ridiculous. It's full of "do/don't do this just because" without explaining why. Your points in "why is it better?" is practically just all saying the same thing: "because it is." Which I do not find helpful. You did explain a little bit at least.
  • Mark
    Mark about 3 years
    print(traceback.format_exc()) is better than traceback.print_tb(exc.__traceback__). print(sys.exc_info()) returns the whole tuple and looks like (<class 'UnicodeDecodeError'>, UnicodeDecodeError('utf-8', b'\x81', 0, 1, 'invalid start byte'), <traceback object at 0x7f179d64ae00>) So indeed traceback.format_exc() is really superior because that prints Traceback (most recent call last): File "<ipython-input-15-9e3d6e01ef04>", line 2, in <module> b"\x81".decode() UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte
  • Azat Aleksanyan
    Azat Aleksanyan almost 3 years
    any ideas how I can use traceback on raspberry pi 0?
  • rjh
    rjh over 2 years
    Good information (I didn't know about logging.exception) but a bit condescending. I think this is due to language barrier rather than malicious intent.
  • joanis
    joanis over 2 years
    2 comments: The use of traceback.print_exc() was already discussed in previous answers. More importantly, why all that mucking about with io.StringIO when those last five lines are exactly equivalent to traceback.print_exc()?
  • mike rodent
    mike rodent over 2 years
    This is good too when dealing with a detected failure of logging... i.e. when for some reason you have failed to create an actual Logger object.
  • omni
    omni over 2 years
    What this guy said. In my company we'll fire anyone who logs using print. /s
  • pcko1
    pcko1 about 2 years
    @joanis I believe those lines are useful if you want to get access to the error body and not just print it. Personally I found it useful because I am recording the stack trace to a database.
  • joanis
    joanis about 2 years
    @pcko1 Thank you for the comment, I'm glad to know there is a good use case for this variant.
  • Jack M
    Jack M about 2 years
    Why does the only way to do this in the language involving passing the same information twice (both exc_obj and exc_obj.__traceback__), and an irrelevant third argument None?