Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Python Exception mail tracker
# coding: utf-8
"""
A Python Exception mail tracker.
https://www.vincentmazeas.com/blog/python-exception-tracker/
"""
import sys
import smtplib
import traceback
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Uncomment this line if you're using the tracker in Maya
# from maya import utils
class ExceptionTracker(object):
"""
Send a email with detailed information when an exception occur.
Use:
>>> ExceptionTracker.activate()
>>> ...
>>> ExceptionTracker.deactivate()
"""
@classmethod
def activate(cls):
"""Activate the tracker."""
# Switch commented/uncommented if you're using the tracker in Maya
sys.excepthook = cls._mail
# utils.formatGuiException = cls._main
@classmethod
def deactivate(cls):
"""Deactivate the tracker."""
# Switch commented/uncommented if you're using the tracker in Maya
sys.excepthook = sys.__excepthook__
# utils.formatGuiException = utils._formatGuiException
# Main sys hook replacement
@classmethod
def _main(cls, exception_type, exception_value, traceback_):
"""Python exception hook that mail exception reports."""
# Build mail contents
mail_contents = "\n".join((
"<DIV>",
"<H3>Traceback</H3>",
cls._format_traceback(exception_type, exception_value, traceback_),
"<H3>Variables</H3>",
cls._format_variables(traceback_),
"</DIV>"
))
# Prepare the mail
subject = "{}: {}".format(exception_type.__name__, exception_value)
your_address = "you@users.com"
user_address = "user@users.com"
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = user_address
msg["To"] = your_address
msg.attach(MIMEText(mail_contents, "html"))
# Send the message - you'll need to enter your own server here
server = smtplib.SMTP("smtp-server.client.com")
server.sendmail(user_address, your_address, msg.as_string())
server.quit()
# Run the basic python error to print the traceback to the console
sys.__excepthook__(exception_type, exception_value, traceback_)
# Private formatters
@classmethod
def _format_traceback(cls, exception_type, exception_value, traceback_):
"""Format the call's traceback with proper indentation."""
# Get call stack text
call_stack = cls._get_traceback_string(
exception_type, exception_value, traceback_
)
html = []
traceback_lines = call_stack.split("\n")
for line in traceback_lines:
# Keep only lines with content
line = line.strip()
if not line:
continue
# Apply offsets to preserve indentation, depending on the line
if line == traceback_lines[0] or line == traceback_lines[-1]:
html.append(' <SPAN>{}</SPAN>'.format(line))
elif line.startswith('File "'):
html.append(' <SPAN style="margin-left: 15px">{}</SPAN>'.format(line))
else:
html.append(' <SPAN style="margin-left: 30px">{}</SPAN>'.format(line))
# Return traceback lines wrapped in a div with monospace font
html = "<br />\n".join(html)
return '<DIV style="font-family: monospace;">\n{}\n</DIV>\n'.format(html)
@classmethod
def _format_variables(cls, traceback_):
"""Return a html table of the local variables names and values."""
# Build table with two columns: key/name & value
table = '<TABLE>\n'
for key, value in sorted(cls._get_exception_variables(traceback_).items()):
table += ' <TR>\n'
table += ' <TD>{} :</TD>\n'.format(key)
# Replacing "<" by "&lt;" to prevent it from being turned into an HTML tag
v = str(value).replace('<', '&lt;')
table += ' <TD>{}</TD>\n'.format(v)
table += ' </TR>\n'
table += '</TABLE>\n'
return table
# Private collectors
@staticmethod
def _get_traceback_string(exception_type, exception_value, traceback_):
"""Return the formatted traceback as a string."""
return '\n'.join(
traceback.format_exception(
exception_type, exception_value, traceback_
)
)
@staticmethod
def _get_exception_variables(traceback_):
"""Return a dict of variables {name: value} from traceback_."""
# Get latest call stack frame (latest_frame is None with SyntaxErrors)
latest_frame = traceback_
while latest_frame and latest_frame.tb_next:
latest_frame = latest_frame.tb_next
# No latest_frame means a SyntaxError which bears no vars
if latest_frame:
return latest_frame.tb_frame.f_locals
return []
# Static class, no need to instantiate
def __init__(self):
raise NotImplementedError("ExceptionTracker is a static class.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment