diff --git a/IPython/CrashHandler.py b/IPython/CrashHandler.py index 8e4b273..c223b41 100644 --- a/IPython/CrashHandler.py +++ b/IPython/CrashHandler.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """sys.excepthook for IPython itself, leaves a detailed report on disk. -$Id: CrashHandler.py 1326 2006-05-25 02:07:11Z fperez $""" +$Id: CrashHandler.py 1828 2006-10-16 02:04:33Z fptest $""" #***************************************************************************** # Copyright (C) 2001-2006 Fernando Perez. @@ -31,14 +31,90 @@ from IPython.genutils import * #**************************************************************************** class CrashHandler: - """sys.excepthook for IPython itself, leaves a detailed report on disk.""" + """Customizable crash handlers for IPython-based systems. - def __init__(self,IP): + Instances of this class provide a __call__ method which can be used as a + sys.excepthook, i.e., the __call__ signature is: + + def __call__(self,etype, evalue, etb) + + """ + + def __init__(self,IP,app_name,contact_name,contact_email, + bug_tracker,crash_report_fname, + show_crash_traceback=True): + """New crash handler. + + Inputs: + + - IP: a running IPython instance, which will be queried at crash time + for internal information. + + - app_name: a string containing the name of your application. + + - contact_name: a string with the name of the person to contact. + + - contact_email: a string with the email address of the contact. + + - bug_tracker: a string with the URL for your project's bug tracker. + + - crash_report_fname: a string with the filename for the crash report + to be saved in. These reports are left in the ipython user directory + as determined by the running IPython instance. + + Optional inputs: + + - show_crash_traceback(True): if false, don't print the crash + traceback on stderr, only generate the on-disk report + + + Non-argument instance attributes: + + These instances contain some non-argument attributes which allow for + further customization of the crash handler's behavior. Please see the + source for further details. + """ + + # apply args into instance self.IP = IP # IPython instance - self.bug_contact = Release.authors['Ville'][0] - self.mailto = Release.authors['Ville'][1] + self.app_name = app_name + self.contact_name = contact_name + self.contact_email = contact_email + self.bug_tracker = bug_tracker + self.crash_report_fname = crash_report_fname + self.show_crash_traceback = show_crash_traceback + + # Hardcoded defaults, which can be overridden either by subclasses or + # at runtime for the instance. + + # Template for the user message. Subclasses which completely override + # this, or user apps, can modify it to suit their tastes. It gets + # expanded using itpl, so calls of the kind $self.foo are valid. + self.user_message_template = """ +Oops, $self.app_name crashed. We do our best to make it stable, but... + +A crash report was automatically generated with the following information: + - A verbatim copy of the crash traceback. + - A copy of your input history during this session. + - Data on your current $self.app_name configuration. + +It was left in the file named: +\t'$self.crash_report_fname' +If you can email this file to the developers, the information in it will help +them in understanding and correcting the problem. + +You can mail it to: $self.contact_name at $self.contact_email +with the subject '$self.app_name Crash Report'. + +If you want to do it now, the following command will work (under Unix): +mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname + +To ensure accurate tracking of this issue, please file a report about it at: +$self.bug_tracker +""" def __call__(self,etype, evalue, etb): + """Handle an exception, call for compatible with sys.excepthook""" # Report tracebacks shouldn't use color in general (safer for users) color_scheme = 'NoColor' @@ -52,60 +128,100 @@ class CrashHandler: rptdir = os.getcwd() if not os.path.isdir(rptdir): rptdir = os.getcwd() - self.report_name = os.path.join(rptdir,'IPython_crash_report.txt') - self.TBhandler = ultraTB.VerboseTB(color_scheme=color_scheme,long_header=1) - traceback = self.TBhandler.text(etype,evalue,etb,context=31) + report_name = os.path.join(rptdir,self.crash_report_fname) + # write the report filename into the instance dict so it can get + # properly expanded out in the user message template + self.crash_report_fname = report_name + TBhandler = ultraTB.VerboseTB(color_scheme=color_scheme, + long_header=1) + traceback = TBhandler.text(etype,evalue,etb,context=31) # print traceback to screen - print >> sys.stderr, traceback + if self.show_crash_traceback: + print >> sys.stderr, traceback # and generate a complete report on disk try: - report = open(self.report_name,'w') + report = open(report_name,'w') except: print >> sys.stderr, 'Could not create crash report on disk.' return - msg = itpl('\n'+'*'*70+'\n' -""" -Oops, IPython crashed. We do our best to make it stable, but... + # Inform user on stderr of what happened + msg = itpl('\n'+'*'*70+'\n'+self.user_message_template) + print >> sys.stderr, msg -A crash report was automatically generated with the following information: - - A verbatim copy of the traceback above this text. - - A copy of your input history during this session. - - Data on your current IPython configuration. + # Construct report on disk + report.write(self.make_report(traceback)) + report.close() -It was left in the file named: -\t'$self.report_name' -If you can email this file to the developers, the information in it will help -them in understanding and correcting the problem. + def make_report(self,traceback): + """Return a string containing a crash report.""" -You can mail it to $self.bug_contact at $self.mailto -with the subject 'IPython Crash Report'. + sec_sep = '\n\n'+'*'*75+'\n\n' -If you want to do it now, the following command will work (under Unix): -mail -s 'IPython Crash Report' $self.mailto < $self.report_name + report = [] + rpt_add = report.append + + rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') + rpt_add('IPython version: %s \n\n' % Release.version) + rpt_add('SVN revision : %s \n\n' % Release.revision) + rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % + (os.name,sys.platform) ) + rpt_add(sec_sep+'Current user configuration structure:\n\n') + rpt_add(pformat(self.IP.rc.dict())) + rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) + try: + rpt_add(sec_sep+"History of session input:") + for line in self.IP.user_ns['_ih']: + rpt_add(line) + rpt_add('\n*** Last line of input (may not be in above history):\n') + rpt_add(self.IP._last_input_line+'\n') + except: + pass -To ensure accurate tracking of this issue, please file a report about it at: -http://projects.scipy.org/ipython/ipython/report -""") - print >> sys.stderr, msg + return ''.join(report) + +class IPythonCrashHandler(CrashHandler): + """sys.excepthook for IPython itself, leaves a detailed report on disk.""" + + def __init__(self,IP): + + # Set here which of the IPython authors should be listed as contact + AUTHOR_CONTACT = 'Ville' + + # Set argument defaults + app_name = 'IPython' + bug_tracker = 'http://projects.scipy.org/ipython/ipython/report' + contact_name,contact_email = Release.authors[AUTHOR_CONTACT][:2] + crash_report_fname = 'IPython_crash_report.txt' + # Call parent constructor + CrashHandler.__init__(self,IP,app_name,contact_name,contact_email, + bug_tracker,crash_report_fname) + + def make_report(self,traceback): + """Return a string containing a crash report.""" sec_sep = '\n\n'+'*'*75+'\n\n' - report.write('*'*75+'\n\n'+'IPython post-mortem report\n\n') - report.write('IPython version: %s \n\n' % Release.version) - report.write('SVN revision : %s \n\n' % Release.revision) - report.write('Platform info : os.name -> %s, sys.platform -> %s' % + + report = [] + rpt_add = report.append + + rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') + rpt_add('IPython version: %s \n\n' % Release.version) + rpt_add('SVN revision : %s \n\n' % Release.revision) + rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % (os.name,sys.platform) ) - report.write(sec_sep+'Current user configuration structure:\n\n') - report.write(pformat(self.IP.rc.dict())) - report.write(sec_sep+'Crash traceback:\n\n' + traceback) + rpt_add(sec_sep+'Current user configuration structure:\n\n') + rpt_add(pformat(self.IP.rc.dict())) + rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) try: - report.write(sec_sep+"History of session input:") + rpt_add(sec_sep+"History of session input:") for line in self.IP.user_ns['_ih']: - report.write(line) - report.write('\n*** Last line of input (may not be in above history):\n') - report.write(self.IP._last_input_line+'\n') + rpt_add(line) + rpt_add('\n*** Last line of input (may not be in above history):\n') + rpt_add(self.IP._last_input_line+'\n') except: pass - report.close() + + return ''.join(report) diff --git a/IPython/ipapi.py b/IPython/ipapi.py index 2ad5658..d25d679 100644 --- a/IPython/ipapi.py +++ b/IPython/ipapi.py @@ -145,6 +145,8 @@ class IPApi: self.user_ns = ip.user_ns + self.set_crash_handler = ip.set_crash_handler + # Session-specific data store, which can be used to store # data that should persist through the ipython session. self.meta = ip.meta diff --git a/IPython/iplib.py b/IPython/iplib.py index ec737f9..0aef214 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -6,7 +6,7 @@ Requires Python 2.3 or newer. This file contains all the classes and helper functions specific to IPython. -$Id: iplib.py 1822 2006-10-12 21:38:00Z vivainio $ +$Id: iplib.py 1828 2006-10-16 02:04:33Z fptest $ """ #***************************************************************************** @@ -543,16 +543,11 @@ class InteractiveShell(object,Magic): # thread (such as in GUI code) propagate directly to sys.excepthook, # and there's no point in printing crash dumps for every user exception. if self.isthreaded: - sys.excepthook = ultraTB.FormattedTB() + ipCrashHandler = ultraTB.FormattedTB() else: from IPython import CrashHandler - sys.excepthook = CrashHandler.CrashHandler(self) - - # The instance will store a pointer to this, so that runtime code - # (such as magics) can access it. This is because during the - # read-eval loop, it gets temporarily overwritten (to deal with GUI - # frameworks). - self.sys_excepthook = sys.excepthook + ipCrashHandler = CrashHandler.IPythonCrashHandler(self) + self.set_crash_handler(ipCrashHandler) # and add any custom exception handlers the user may have specified self.set_custom_exc(*custom_exceptions) @@ -769,6 +764,22 @@ class InteractiveShell(object,Magic): #setattr(self.hooks,name,new.instancemethod(hook,self,self.__class__)) + def set_crash_handler(self,crashHandler): + """Set the IPython crash handler. + + This must be a callable with a signature suitable for use as + sys.excepthook.""" + + # Install the given crash handler as the Python exception hook + sys.excepthook = crashHandler + + # The instance will store a pointer to this, so that runtime code + # (such as magics) can access it. This is because during the + # read-eval loop, it gets temporarily overwritten (to deal with GUI + # frameworks). + self.sys_excepthook = sys.excepthook + + def set_custom_exc(self,exc_tuple,handler): """set_custom_exc(exc_tuple,handler) diff --git a/doc/ChangeLog b/doc/ChangeLog index 5924819..ad788b8 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,14 @@ +2006-10-15 Fernando Perez + + * IPython/ipapi.py (IPApi.__init__): Added new entry to public + api: set_crash_handler(), to expose the ability to change the + internal crash handler. + + * IPython/CrashHandler.py (CrashHandler.__init__): abstract out + the various parameters of the crash handler so that apps using + IPython as their engine can customize crash handling. Ipmlemented + at the request of SAGE. + 2006-10-14 Ville Vainio * Magic.py, ipython.el: applied first "safe" part of Rocky