Hide traceback unless a debug flag is set
Asked Answered
D

3

74

What is the idiomatic python way to hide traceback errors unless a verbose or debug flag is set?

Example code:

their_md5 = 'c38f03d2b7160f891fc36ec776ca4685'
my_md5 = 'c64e53bbb108a1c65e31eb4d1bb8e3b7' 
if their_md5 != my_md5:
    raise ValueError('md5 sum does not match!')

Existing output now, but only desired when called with foo.py --debug:

Traceback (most recent call last):
  File "b:\code\apt\apt.py", line 1647, in <module>
    __main__.__dict__[command] (packages)
  File "b:\code\apt\apt.py", line 399, in md5
    raise ValueError('md5 sum does not match!')
ValueError: md5 sum does not match!

Desired normal output:

ValueError: md5 sum does not match!

Here's a test script: https://gist.github.com/maphew/e3a75c147cca98019cd8

Dak answered 28/12, 2014 at 7:52 Comment(0)
C
100

The short way is using the sys module and use this command:

sys.tracebacklimit = 0

Use your flag to determine the behaviour.

Example:

>>> import sys
>>> sys.tracebacklimit=0
>>> int('a')
ValueError: invalid literal for int() with base 10: 'a'

The nicer way is to use and exception hook:

def exception_handler(exception_type, exception, traceback):
    # All your trace are belong to us!
    # your format
    print "%s: %s" % (exception_type.__name__, exception)

sys.excepthook = exception_handler

Edit:

If you still need the option of falling back to the original hook:

def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook):
    if _your_debug_flag_here:
        debug_hook(exception_type, exception, traceback)
    else:
        print "%s: %s" % (exception_type.__name__, exception)

Now you can pass a debug hook to the handler, but you'll most likely want to always use the one originated in sys.excepthook (so pass nothing in debug_hook). Python binds default arguments once in definition time (common pitfall...) which makes this always work with the same original handler, before replaced.

Cavie answered 28/12, 2014 at 7:53 Comment(6)
thank you! I'm having difficulty implementing the nicer way though. It's perfect for the user friendly response, but I haven't been able to do it verbosely. print traceback just shows object name and dir(traceback) doesn't show anything I know what to do with. Here's what I have: gist.github.com/maphew/e3a75c147cca98019cd8Dak
Rev5 of the sample gist has a complete working example. Thanks again Reut.Dak
Beautiful solution. You can make it into a one-liner using a lambda: sys.excepthook = lambda exctype,exc,traceback : print("{}: {}".format(exctype.__name__,exc))Atronna
python3 alert: The above example with sys.tracebacklimit = 0 does not work. However, the defined (and insightful!) function exceptionHandler works just fine, with the usual change in print.Sd
Instead of relying on sys.excepthook not beeing re-defined at function definition time, you can use sys.__excepthook__, which always keeps its original definition, like in this answer.Revenant
This really should be the default behaviour - adding a page of traceback makes the user less likely to look any error message, so is quite counter productive unless you are the actual developer of the code.Sleepwalk
I
6
try:
    pass # Your code here
except Exception as e:
    if debug:
        raise # re-raise the exception
              # traceback gets printed
    else:
        print("{}: {}".format(type(e).__name__, e))
Interdict answered 28/12, 2014 at 19:51 Comment(3)
that yields TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType for me (py v2.7.4 at the moment)Dak
@matt: in which line? I don't have 2.7.4, so I cannot see the error.Interdict
my mistake, I had if debug: raise outside of the except ... as e: block. This now works in repl.it/languages/Python. +1 for something that works, but I'm accepting Reut's answer because I'd prefer not to have to wrap everything in try:...except:.Dak
L
1

Use the logging system to handle the error output.

I'll use requests connection error as an example use case, it generates a cascade of 3 exceptions each with a lengthy traceback. Only the final error message is of real importance - the called service is refusing, I don't need to know 3 pages of traceback from my client app!

  1. Define a custom error class
# file: custom_exceptions.py

class Error(Exception):
    """This class should be used where traceback should not be logged"""
    pass
  1. In the inner function, catch the exception and cast it to the custom error class
    try:
        response = requests.get(url, auth=auth, headers=headers, timeout=TIMEOUT)
    except requests.exceptions.ConnectionError as e:
        raise custom_exceptions.Error(e)
  1. Handle the custom exception and unexpected exceptions differently in the caller function
    except custom_exceptions.Error as e:    # any custom error based on the custom Error class
        if LOGLEVEL=logging.DEBUG:
            logger.exception(e)     # with traceback
        else:
            logger.error(e)         # just the exception and message
        exit()
    except Exception as e:
        logger.exception(e)         # with traceback
        exit()

The resultant log message - all the detail you need for this error scenario:

2021-07-23 10:58:27,545 [ERROR] HTTPConnectionPool(host='localhost', port=8080): Max retries exceeded with url: /next (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fc54f11f9d0>: Failed to establish a new connection: [Errno 111] Connection refused'))
Lula answered 23/7, 2021 at 9:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.