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
.
In order of increasing severity, these are the logging levels:
logging.DEBUG
: All messages are shown, useful for debugging your code.logging.INFO
: Info, and messages of greater severity shownlogging.WARNING
: Warning messages and abovelogging.ERROR
: Error messages which might have been caught by try-except, as well as critical errorslogging.CRITICAL
: Errors which will likely terminate the programBefore 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.
Allow less secure apps
to On
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"]
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
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.
Note you can comment without any login by: