Simple Error Reporter
A simple way to allow users to report errors on scripts and tools.
To use, wrap your code in the Report() class.
Either as a Decorator:
def myfunc ():
or as a context manager:
with Report ():
Wrap the outermost code that will run and any errors passing through will prompt the user to email a brief report. The report is brief (would prefer to use cgitb) as there is a small finite limit to “mailto:” urls.
For general purpose, easy, small time reporting it does its job. :)
# Simple Error report mechanism squeezing into mailto: character limit
# Created By Jason Dixon. http://internetimagery.com
# Wrap the outermost function calls in the Report class
# As a decorator or as a context manager on the outermost function calls
# For instance, decorate your Main() function,
# or any function that is called directly by a GUI
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
### Change these variables to suit ###
CONTACT = "firstname.lastname@example.org" # Who will be emailed?
SUBJECT = "Error Report for SCRIPTNAME" # Subject of the email
CONFIRM_MSG = """
There was a problem and a brief error report has been created.
Would you like to send it?
""" # Message in confirm dialog
OVERSIZE_MSG = "Report Continues..." # Message if report is too long and cut off
### Don't change things beyond this point ###
class Report ( object ):
""" Report Errors """
depth = 0 # Follow report depth
def __init__ ( s , char_limit = 2083 ):
s . char_limit = char_limit # Max characters for mailto: (0 = no limit)
def __call__ ( s , func ):
""" Decorate a function. Capture and report any errors """
@functools.wraps ( func )
def inner ( * args , ** kwargs ):
with s :
return func ( * args , ** kwargs )
def __enter__ ( s ): Report . depth += 1
def __exit__ ( s , eType , eVal , eTrace ):
""" Report Errors if they happened """
Report . depth -= 1
if eType and not Report . depth : # We have an error?
if s . consent ( eType , eVal ):
text = [
str ( datetime . datetime . now ()),
platform . platform (),
" % s: % s" % ( eType . __name__ , eVal ),
s . software ()
text += list ( s . compact_trace ( eTrace ))
url = "mailto: % s?subject= % s&body= % s" % (
urllib . quote ( SUBJECT ),
urllib . quote ( " \n " . join ( text ))
if s . char_limit and s . char_limit < len ( url ): # We've gone too big. Note that at the bottom
note = urllib . quote ( " \n\n % s" % OVERSIZE_MSG )
url = url [: s . char_limit - len ( note )] + note
webbrowser . open ( url ) # Open email!
def consent ( s , eType , eVal ):
""" Ask user to consent to send message """
import maya.cmds as cmds # Is Maya active? Ask using their GUI
answer = cmds . confirmDialog ( t = eType . __name__ , m = CONFIRM_MSG , b = ( "Yes" , "No" ), db = "Yes" , cb = "No" , ds = "No" )
return "Yes" == answer
except ImportError :
return True # No means to ask? Ah well ...
def software ( s ):
""" Return information about software """
import maya.mel as mel
version = mel . eval ( "$tmp = getApplicationVersionAsFloat();" )
return "Maya, % s" % version
except ImportError :
return "Unknown software."
def compact_trace ( s , trace ):
""" Format traceback compactly """
filepath = None
for frame , path , line , func , context , i in reversed ( inspect . trace ()):
code = context [ i ] . strip ()
# Tell us which file and function we are in!
if filepath == path : # Skip repeating filename
yield "In \" % s \" :" % func
filepath = path
yield "In \" % s \" % s:" % ( func , path )
# Tell us the line number and code
yield "< % s> % s" % ( line , code )
# Tell us the value of relevant variables (attributes using dot notation)
all_vars = dict ( frame . f_globals , ** frame . f_locals )
tokens = set ( re . split ( r"[^\w\.]+" , code ))
tokens |= set ( b for a in tokens for b in a . split ( "." )) # Add in partial names
for a , b in all_vars . iteritems ():
for var , val in s . collect_vars ( tokens , a , b ):
if var != func :
yield " % s= % s" % ( var , repr ( val ))
def collect_vars ( s , code , var , val ):
""" Collect relevant variables """
if var in code :
yield var , val
for attr in dir ( val ):
for a in s . collect_vars ( code , "." . join (( var , attr )), getattr ( val , attr )):
if __name__ == '__main__' :
# Some example usage!
@Report () # As Decorator
def recurse ( num ):
1 / num
recurse ( num - 1 )
with Report (): # As Context manager
recurse ( 5 )