Sending application exception notifications via Mailgun.

Python logging handler for Mailgun

Sander Sink

Yet another reason to love python is the wonderful logging API provided by a standard library. Having a decent logger provided by the language saves developers time from integrating with some third-party library, but most importantly it contributes to a clean system where all modules use the same logger. Out of the box, there is a handler for almost any typical use-case, but the best feature is the option to create your own with relative ease. So-far we’ve been using SMTPHandler, but since moving to Mailgun, I decided to experiment with a custom handler to submit logs via their API instead. I’d like to mention that creating a custom logging handler is not a requirement, it is still possible to use their SMTP server with SMTP handler. To get started, we need to extend Handler baseclass and override emit method to make a POST request.

import logging
import requests

class MailgunHandler(logging.Handler):

    def __init__(self, api_url, api_key, sender, recipients, subject):
        # run the regular Handler __init__
        self.api_url = api_url
        self.api_key = api_key
        self.sender = sender
        self.recipients = recipients
        self.subject = subject

    def emit(self, record):
        # record.message is the log message
        for recipient in self.recipients:
            data = {
                "from": self.sender,
                "to": recipient,
                "subject": self.subject,
                "text": self.format(record)
  , auth=("api", self.api_key), data=data)

I have created a simple flask application that registers the handler and also implemented a route that triggers an error to test it out.

import logging
from flask import Flask
from mailgun_handler import MailgunHandler

app = Flask(__name__)
# Setup logging handler
mail_handler = MailgunHandler(
    sender='[email protected]',
    recipients=['[email protected]', '[email protected]'],
    subject='Application error!'
    Message type:       %(levelname)s
    Location:           %(pathname)s:%(lineno)d
    Module:             %(module)s
    Function:           %(funcName)s
    Time:               %(asctime)s

def error():
    raise Exception("Beds are burning!")
if __name__ == '__main__': #Handler isn't executed if app is run in debug mode