Extending Python Logger for mailing Exceptions
satyamsoni2211
Posted on July 22, 2022
Logging is most crucial part of any application or process you create, as it helps you debug or keep track of whats going on. Logging is a very vast topic in itself but super useful when you want to perform some side effects or redirect your output to some other services or perform some side computations etc.
Highly configurable by design, we can add extend functionalities of existing loggers, using custom Handlers
.
We will try to extend functionality of existing logger to e-mail
exceptions occurred during code execution. Let us create a python Logger
using built-in library logging
.
import logging
logger: logging.Logger = logging.getLogger()
Above code will return a root
logger instance, since we are not mentioning any name explicitly. If you want a named logger
instance, you can pass name
to getLogger
function.
Let us now create a custom Handler
to handle records and perform some side effect. We can inherit from logging.Handler
abstract base class to create our custom Handler.
logging.Handler
base class provides multiple hooks that you can override. We will override emit
hook for our requirement.
import logging
class MailHandler(logging.Handler):
def emit(self, record: logging.LogRecord) -> None:
if record.exc_info:
exception = "".join(traceback.format_exception(*record.exc_info))
else:
exception = "".join(traceback.format_exception(*sys.exc_info()))
self._send_mail(exception)
We have added set of statements to intercept exception from the record and create formatted stack trace
.
emit
hook will receive logging.LogRecord
which will contain all the details regarding the record like message, timestamp, line no, exception info etc. We have also added a instance method _send_mail
to send formatted stack trace to the user.
Let us now complete _send_mail
function. We will use AWS SES
for sending e-mail. You may also use smtp
as an alternative.
import boto3
class MailHandler(logging.Handler):
def _send_mail(self, message):
self.client = boto3.client('ses')
ses_arn = os.getenv('SES_ARN')
source = os.getenv('SES_SOURCE')
html = f"""
<p>Exception occurred while execution. Please check. </p>
<pre>{message}</pre>
"""
self.client.send_email(
Source=source,
Destination={
'ToAddresses': [
'foobar@gmail.com',
],
},
Message={
'Subject': {
'Data': 'Exception occurred while executing Lambda. Please check.',
},
'Body': {
'Html': {
'Data': html
}
}
},
SourceArn=ses_arn
)
We are using boto3
library for connecting to SES
and send e-mail.
I am reading ses_arn
and source
from environment variables. These will be required to send e-mail to the destination address using your configured SES
record.
We are done with creating our custom handler. Let us register this with our logger instance.
import logging
logger: logging.Logger = logging.getLogger()
handler = MailHandler()
handler.setLevel(logging.ERROR)
logger.logger_.addHandler(handler)
We have registered our custom handler with our logger instance. It will be only activated on error
record type as we have set level to logging.ERROR
. You may now test your custom handler as below.
logger.error(Exception("Fake Exception"))
You should receive an email, with exception
and stack trace.
Below is the complete code for custom handler.
import boto3
import logging
class MailHandler(logging.Handler):
def emit(self, record: logging.LogRecord) -> None:
if record.exc_info:
exception = "".join(traceback.format_exception(*record.exc_info))
else:
exception = "".join(traceback.format_exception(*sys.exc_info()))
self._send_mail(exception)
def _send_mail(self, message):
self.client = boto3.client('ses')
ses_arn = os.getenv('SES_ARN')
source = os.getenv('SES_SOURCE')
html = f"""
<p>Exception occurred while execution. Please check. </p>
<pre>{message}</pre>
"""
self.client.send_email(
Source=source,
Destination={
'ToAddresses': [
'foobar@gmail.com',
],
},
Message={
'Subject': {
'Data': 'Exception occurred while executing Lambda. Please check.',
},
'Body': {
'Html': {
'Data': html
}
}
},
SourceArn=ses_arn
)
logger: logging.Logger = logging.getLogger()
handler = MailHandler()
handler.setLevel(logging.ERROR)
logger.logger_.addHandler(handler)
#raising exception
try:
raise Exception("Fake Exception")
except Exception as e:
logger.error(e, exc_info=True)
You can now customize logging handlers has per your requirements.
Happy Logging 😊.
Posted on July 22, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.