Python logging - Sending email alerts

April 21, 2018, 4:59 p.m.

Python's logging library is incredibly useful for debugging your code, and monitoring the health of scripts that are running, and keeping historical logs of events occurring in your scripts.

It also offers a very useful function for piping log messages to you by email. This is especially useful for being alerted when your script has run into severe problems that you should address as soon as possible.

This blog post will go through some code for implementing this feature.

We will start by creating some settings for logging.

import logging

# create the default logging level
logging.basicConfig(format='%(asctime)s %(levelname)s %(name)s: %(message)s',
                    level=logging.WARNING,
                    filename='log.log',  # file to pipe the logging output to
                    datefmt='%Y-%m-%d %H:%M:%S'
                    )

# create local logger with its own log level
logger = logging.getLogger('myapp')
logger.setLevel(logging.INFO)

We set the default logging level to WARNING, so we will not receive anything of lower severity from imported libraries. We also specified that we want log events to be piped to a log file, formatted as follows:

2018-04-21 16:12:20 ERROR myapp: Ohh no! Something bad happened!

The code also created a separate logger for our local script, calling its scope "myapp", and setting the severity level to INFO.

We can now create a log messages

logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")

In our log.log file we will find:

2018-04-21 16:17:40 INFO myapp: This is an info message
2018-04-21 16:17:40 WARNING myapp: This is a warning message
2018-04-21 16:17:41 ERROR myapp: This is an error message
2018-04-21 16:17:42 CRITICAL myapp: This is a critical message

Notice how the debug message was not sent to the file. This is because we set the local logger to filter out messages of lower severity than info.

Summary of Levels

In order of increasing severity, these are the logging levels:

Setting up email credentials

Before we can send emails we need to get the credentials information for the email service we will be using. I would recommend storing these credentials in a separate file such as a JSON file, rather than hard-coding them into your script. Save the following in a file called credentials.json in the same working directory, and modify the details to fit your email account and service.

{
    "smtp_host": "smtp.gmail.com",
    "smtp_port": 587,
    "user": "MYEMAIL@gmail.com",
    "password": "MYPASSWORD"
}

If you are using Gmail, then you will need to also need to change one of the security settings in Gmail before python can get access to its SMTP server.

Load up the credentials information into your script.

import json

# GET CREDENTIALS
with open("credentials.json", mode="r") as f:
    credentials = json.load(f)

USER = credentials["user"]
PASSWORD = credentials["password"]
HOST = credentials["smtp_host"]
PORT = credentials["smtp_port"]

Sending log messages to email

We can make use of the SMTPHandler in the logging library to send log messages to email. We will configure it so that we only send out email messages when the logs are of ERROR or greater severity level.

# SETTINGS
TLS = True   # does smtp server use TLS encryption? True for Gmail
RECIPIENTS = ["tony.stark@avengers.com", "starlord@guardians.com"]
SUBJECT = "ERROR: with my app" # email subject heading

# ADD EMAIL HANDLER
email_handler = logging.handlers.SMTPHandler(mailhost=(HOST, PORT),
                                            credentials=(USER, PASSWORD),
                                            fromaddr=USER,
                                            toaddrs=RECIPIENTS,
                                            subject=SUBJECT,
                                            secure=() if TLS else None,
                                            )
email_handler.setLevel(logging.ERROR)
logger.addHandler(email_handler)

We can now create some logs of different levels of intensity again.

logger.debug("This is a debug message")
logger.info("This is a info message")
logger.warning("This is a warning message")
logger.error("This is a error message")
logger.critical("This is a critical message")

In the log.log file, the following lines were added:

2018-04-21 16:38:32 INFO myapp: This is an info message
2018-04-21 16:38:32 WARNING myapp: This is a warning message
2018-04-21 16:38:32 ERROR myapp: This is an error message
2018-04-21 16:38:37 CRITICAL myapp: This is a critical message

And the recipients listed in RECIPIENTS received the following messages via email as two separate emails.

2018-04-21 16:38:32 ERROR myapp: This is an error message
2018-04-21 16:38:37 CRITICAL myapp: This is a critical message

Further improvements

The script will hang while it sends the emails, which can sometimes take a while. It might be better to send them using a separate thread. This section of the documentation might be useful.

Comments

Note you can comment without any login by:

  1. Typing your comment
  2. Selecting "sign up with Disqus"
  3. Then checking "I'd rather post as a guest"