Extending Python Logger for mailing Exceptions
Tags: python, logging, tutorial, opensource
Published: 2022-07-22 07:25:11

---
title: Extending Python Logger for mailing Exceptions
published: true
description: 
tags: python, logging, tutorial, opensource
cover_image: https://python.land/wp-content/uploads/2021/12/python-try-except.jpg
# Use a ratio of 100:42 for best results.
---

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`.

```python
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.

```python
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.

```python
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`](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) 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.

```python
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.

```python
logger.error(Exception("Fake Exception"))
```

You should receive an email, with `exception` and *stack trace*.

Below is the complete code for custom handler.

```python
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 😊.