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 valueParentFunction
will trigger and exception and send its own error message in the e-mail alertIn 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
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()
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.
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!'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With