71

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

matt wilkie
  • 17,268
  • 24
  • 80
  • 115

4 Answers4

93

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.

Zitrax
  • 19,036
  • 20
  • 88
  • 110
Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
  • 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: https://gist.github.com/maphew/e3a75c147cca98019cd8 – matt wilkie Dec 28 '14 at 19:13
  • Rev5 of the [sample gist](https://gist.github.com/maphew/e3a75c147cca98019cd8/7236687e4161b2c3c5eca0daec500a516cc21055) has a complete working example. Thanks again Reut. – matt wilkie Dec 28 '14 at 23:45
  • 4
    Beautiful solution. You can make it into a one-liner using a lambda: `sys.excepthook = lambda exctype,exc,traceback : print("{}: {}".format(exctype.__name__,exc))` – András Aszódi Mar 19 '15 at 14:46
  • 3
    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. – pbarill Jul 23 '15 at 14:16
  • 3
    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](http://stackoverflow.com/a/6598286/512111). – j08lue Nov 14 '16 at 07:14
  • 2
    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. – Chris Oct 26 '17 at 23:19
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))
GingerPlusPlus
  • 5,336
  • 1
  • 29
  • 52
  • that yields `TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType` for me (py v2.7.4 at the moment) – matt wilkie Dec 28 '14 at 20:18
  • @matt: in which line? I don't have 2.7.4, so I cannot see the error. – GingerPlusPlus Dec 28 '14 at 20:39
  • my mistake, I had `if debug: raise` outside of the `except ... as e:` block. [This](http://stackoverflow.com/a/27680034/14420) now works in http://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:_. – matt wilkie Dec 28 '14 at 22:23
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'))
hi2meuk
  • 1,080
  • 11
  • 9
-1

I'll answer wickedly:

Don't hide tracebacks

Traceback is to show developer an error - unhandled exception. Something that delelopers didn't prepare, didn't notice. In one word - abomination.

But you are the clever one. You see that there are cases when md5 sum will not much, and than you shall end your program. So maybe let's do exaclty that:

if their_md5 != my_md5:
  sys.stderr.write('md5 sum does not match!')
  exit(-1)

What is more - your fellow devs would be thankfull - they will still have their tracebacks and will never try to handle such exception. Shortly - there is no reason to raise exception if you don't want to handle it.

But...

if you really have to raise exception (so maybe there will be some cases that would be acceptable, and your fellow dev would like to handle it, e.g. with sha256, or it would be small part of gigantic project) than you can do this:

 def do_sth_with_md5(their_md5, my_md5):
     if their_md5 != my_md5:
         raise ValueError('md5 sum does not match!')
     actual_staff_to_do(their_md5, my_md5)
    (...)

 ... somewhere else ...
 try:
     do_sth_with_md5(their, my)
 except ValueError:
     sys.stderr.write('md5 sum does not match!') #or sha256handling... whatever
     exit(-1)

     

Of course those are simplified examples...

ravenwing
  • 668
  • 3
  • 20
  • exit(-1) doesnt work if you are in Spyder. As far as I know, raise is the only way out. – Tunneller Jan 25 '21 at 16:24
  • @Tunneller , what the heck is Spyder? Do you mean this IDE (https://www.spyder-ide.org/)? – ravenwing Jan 29 '21 at 15:38
  • Hm, I want to say "yes" but Stackoverflow appears to want me to type a complete sentence? So @ravenwing, "yes". – Tunneller Feb 07 '21 at 23:38
  • So... I'll say that it is python answer, not some-IDE-specyfic-answer. I'm pretty sure that it doesn't use it's own implementation of python. So... change your IDE ;p – ravenwing Feb 08 '21 at 01:55