Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture Nested Exceptions in Python

I have set of Python scripts which are calling functions in a nested way. For each of these functions I have a try, except statement to capture every exception and print them. I would like to send an e-mail alert containing the complete sequence of exceptions encountered during the execution. Example:

import sys

def SendAlert(ErrorMessage):
    try:
        #send email alert with error message
        #[...]
    except:
        print(str(sys.exc_info()))
        return(sys.exc_info())

def ParentFunction():
    try:
        #call ChildFunction
        ChildResult = ChildFunction()

        #do stuff with ChildResult
        #[...]
        return ParentResult
    except:
        ErrorMessage = str(sys.exc_info())
        print(ErrorMessage)
        SendAlert(ErrorMessage)

def ChildFunction():
    try:
        #do stuff
        #[...]
        return ChildResult
    except:
        print(str(sys.exc_info()))
        return(sys.exc_info())

#main
if __name__ == '__main__':
    Result = ParentFunction()

The code above would behave as follow in case of error in ChildFunction which is the most nested function:

  • ChildFunction encounters an exception it will print it and return the error message to ParentFunction
  • ParentFunction will fail because ChildResult contains an error message and not a valid value
  • ParentFunction will trigger and exception and send its own error message in the e-mail alert

In addition to the error message from ParentFunction, I would like the e-mail alert to contain the error message from ChildFunction. Note that I would like to avoid passing ChildResult variable to SendAlert function in the except statement of ParentFunction because in real life my program has a lot of nested functions and it would mean passing the result variable of every single function into the except statement.

How would you achieve this? Is there a way to access the complete sequence of errors triggered by the whole program?

Thanks

like image 956
Alexis.Rolland Avatar asked Mar 08 '23 22:03

Alexis.Rolland


2 Answers

you don't need to return exceptions obtained with sys.exc_info: we can just re-raise it

try:
    # do stuff
# FIXME: we should avoid catching too broad exception
except Exception as err:
    # do stuff with exception
    raise err

so your example may look like

def SendAlert(ErrorMessage):
    try:
        # send email alert with error message
        # [...]
        pass
    # what kind of exceptions may occur while sending email?
    except Exception as err:
        print(err)
        raise err


def ParentFunction():
    try:
        # call ChildFunction
        ChildResult = ChildFunction()

        ParentResult = ChildResult
        # do stuff with ChildResult
        # [...]
        return ParentResult
    # FIXME: we should avoid catching too broad exception
    except Exception as err:
        ErrorMessage = str(err)
        # why do we need to print again?
        print(ErrorMessage)
        SendAlert(ErrorMessage)


def ChildFunction():
    try:
        ChildResult = 0
        # do stuff
        # [...]

        # let's raise `ZeroDivisionError`

        ChildResult /= 0

        return ChildResult
    # FIXME: we should avoid catching too broad exception
    except Exception as err:
        print(err)
        raise err


# main
if __name__ == '__main__':
    Result = ParentFunction()

Further improvements

For printing full error traceback we can use logging module like

import logging

logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)


def SendAlert(ErrorMessage):
    try:
        # send email alert with error message
        # [...]
        pass
    # what kind of exceptions may occur while sending email?
    except Exception as err:
        logger.exception('Error while sending email')
        # we're not receiving values from this function
        raise err


def ParentFunction():
    try:
        # call ChildFunction
        ChildResult = ChildFunction()

        ParentResult = ChildResult
        # do stuff with ChildResult
        # [...]
        return ParentResult
    # FIXME: we should avoid catching too broad exception
    except Exception as err:
        # this will log full error traceback
        # including `ChildFunction`
        logger.exception('Error in ParentFunction')
        ErrorMessage = str(err)
        SendAlert(ErrorMessage)


def ChildFunction():
    ChildResult = 0
    # do stuff
    # [...]

    # e. g. let's raise `ZeroDivisionError`
    ChildResult /= 0

    return ChildResult


# main
if __name__ == '__main__':
    Result = ParentFunction()

And it is just the tip of the iceberg, logging is awesome and you definitely should use it.

Further reading

  • Logging HOWTO,
  • SMTPHandler for sending errors on email via SMTP
like image 125
Azat Ibrakov Avatar answered Mar 18 '23 08:03

Azat Ibrakov


You can, also, create a custom exception that can take a descriptive error message and return it.

Here is, a trivial example that you can modify and implement it into your code untill it fills your needs:

class MyCustomError(Exception):
    def __init__(self, err):
        Exception.__init__(self)
        self.error = err
    def __str__(self):
        return "%r" % self.error

a = 1
try:
    if a != 0:
        raise MyCustomError("This is an Error!")
except MyCustomError as err:
    print(err)

Output:

'This is an Error!'
like image 37
Chiheb Nexus Avatar answered Mar 18 '23 07:03

Chiheb Nexus