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

后端 未结 15 2055
天命终不由人
天命终不由人 2020-11-22 13:46

I want to catch and log exceptions without exiting, e.g.,

try:
    do_stuff()
except Exception, err:
    print(Exception, err)
    # I want to print the entir         


        
相关标签:
15条回答
  • 2020-11-22 14:21

    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!")
    
    0 讨论(0)
  • 2020-11-22 14:24

    I don't see this mentioned in any of the other answers. If you're passing around an Exception object for whatever reason...

    In Python 3.5+ you can get a trace from an Exception object using traceback.TracebackException.from_exception(). For example:

    import traceback
    
    
    def stack_lvl_3():
        raise Exception('a1', 'b2', 'c3')
    
    
    def stack_lvl_2():
        try:
            stack_lvl_3()
        except Exception as e:
            # raise
            return e
    
    
    def stack_lvl_1():
        e = stack_lvl_2()
        return e
    
    e = stack_lvl_1()
    
    tb1 = traceback.TracebackException.from_exception(e)
    print(''.join(tb1.format()))
    

    However, the above code results in:

    Traceback (most recent call last):
      File "exc.py", line 10, in stack_lvl_2
        stack_lvl_3()
      File "exc.py", line 5, in stack_lvl_3
        raise Exception('a1', 'b2', 'c3')
    Exception: ('a1', 'b2', 'c3')
    

    This is just two levels of the stack, as opposed to what would have been printed on screen had the exception been raised in stack_lvl_2() and not intercepted (uncomment the # raise line).

    As I understand it, that's because an exception records only the current level of the stack when it is raised, stack_lvl_3() in this case. As it's passed back up through the stack, more levels are being added to its __traceback__. But we intercepted it in stack_lvl_2(), meaning all it got to record was levels 3 and 2. To get the full trace as printed on stdout we'd have to catch it at the highest (lowest?) level:

    import traceback
    
    
    def stack_lvl_3():
        raise Exception('a1', 'b2', 'c3')
    
    
    def stack_lvl_2():
        stack_lvl_3()
    
    
    def stack_lvl_1():
        stack_lvl_2()
    
    
    try:
        stack_lvl_1()
    except Exception as exc:
        tb = traceback.TracebackException.from_exception(exc)
    
    print('Handled at stack lvl 0')
    print(''.join(tb.stack.format()))
    

    Which results in:

    Handled at stack lvl 0
      File "exc.py", line 17, in <module>
        stack_lvl_1()
      File "exc.py", line 13, in stack_lvl_1
        stack_lvl_2()
      File "exc.py", line 9, in stack_lvl_2
        stack_lvl_3()
      File "exc.py", line 5, in stack_lvl_3
        raise Exception('a1', 'b2', 'c3')
    

    Notice that the stack print is different, the first and last lines are missing. Because it's a different format().

    Intercepting the exception as far away from the point where it was raised as possible makes for simpler code while also giving more information.

    0 讨论(0)
  • 2020-11-22 14:25

    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.

    0 讨论(0)
提交回复
热议问题