5

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

4
  • 1
    what about re-raising exceptions? Commented Jun 3, 2017 at 3:07
  • What do you mean? Commented Jun 3, 2017 at 3:07
  • 1
    Why not using try ... except Exception as error ? Commented Jun 3, 2017 at 3:23
  • 1
    Thanks Chiheb, that's what Azat recommended below too. I will try. Commented Jun 3, 2017 at 3:29

2 Answers 2

8

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the further improvements, I have just tested the script above. One question, since the script is re-raising and logging the exceptions in ParentFunction with the complete sequence of exceptions, maybe I don't need to log it too in ChildFunction? Otherwise the log would get quite spammy with a lot of redundancy and hence less readable. What do you think?
@Alexis.Rolland: ofc you should not log same exception twice, so feel free to remove logging inside of ChildFunction
Ok, sorry if this sounds a stupid question but what if I litterally remove the try... except clauses from ChildFunction? Would ParentFunction still be able to do the logging? (Can't test now I'm on my phone)
I mean do the logging of the complete sequence of exceptions of course... :)
1

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!'

3 Comments

Thanks Chiheb, I'm kind of new to classes, could you please elaborate on what it does here and its benefit?
With this kind of custom exceptions (classes), you can trace easly the error within your code, also, you can add other repetitive codes into your classes. For example, when there is an exception, you want to log the error into a file then continue processing the rest of your code. Or maybe, when there is an exception you want to call a method/function before executing the rest of your code. There is no limitations ... Also this is a handy trick to classify the exceptions into groups. etc...
See here for more informations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.