CrashHandler.py
230 lines
| 8.3 KiB
| text/x-python
|
PythonLexer
/ IPython / CrashHandler.py
fperez
|
r0 | # -*- coding: utf-8 -*- | ||
"""sys.excepthook for IPython itself, leaves a detailed report on disk. | ||||
Fernando Perez
|
r1875 | |||
Authors | ||||
------- | ||||
- Fernando Perez <Fernando.Perez@berkeley.edu> | ||||
Fernando Perez
|
r1853 | """ | ||
fperez
|
r0 | |||
#***************************************************************************** | ||||
Fernando Perez
|
r1875 | # Copyright (C) 2008-2009 The IPython Development Team | ||
# Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> | ||||
fperez
|
r0 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#***************************************************************************** | ||||
#**************************************************************************** | ||||
# Required modules | ||||
# From the standard library | ||||
fperez
|
r52 | import os | ||
import sys | ||||
fperez
|
r0 | from pprint import pprint,pformat | ||
Fernando Perez
|
r1875 | # Our own | ||
from IPython import Release | ||||
fperez
|
r0 | from IPython import ultraTB | ||
Fernando Perez
|
r1875 | from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names | ||
from IPython.Itpl import Itpl,itpl,printpl | ||||
fperez
|
r46 | from IPython.genutils import * | ||
fperez
|
r0 | |||
#**************************************************************************** | ||||
class CrashHandler: | ||||
fptest
|
r381 | """Customizable crash handlers for IPython-based systems. | ||
fperez
|
r0 | |||
fptest
|
r381 | 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 | ||||
fperez
|
r0 | self.IP = IP # IPython instance | ||
fptest
|
r381 | 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 | ||||
""" | ||||
fperez
|
r0 | |||
def __call__(self,etype, evalue, etb): | ||||
fptest
|
r381 | """Handle an exception, call for compatible with sys.excepthook""" | ||
fperez
|
r0 | |||
# Report tracebacks shouldn't use color in general (safer for users) | ||||
color_scheme = 'NoColor' | ||||
# Use this ONLY for developer debugging (keep commented out for release) | ||||
#color_scheme = 'Linux' # dbg | ||||
try: | ||||
rptdir = self.IP.rc.ipythondir | ||||
except: | ||||
rptdir = os.getcwd() | ||||
if not os.path.isdir(rptdir): | ||||
rptdir = os.getcwd() | ||||
fptest
|
r381 | 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) | ||||
fperez
|
r0 | |||
# print traceback to screen | ||||
fptest
|
r381 | if self.show_crash_traceback: | ||
print >> sys.stderr, traceback | ||||
fperez
|
r0 | |||
# and generate a complete report on disk | ||||
try: | ||||
fptest
|
r381 | report = open(report_name,'w') | ||
fperez
|
r0 | except: | ||
print >> sys.stderr, 'Could not create crash report on disk.' | ||||
return | ||||
fptest
|
r381 | # Inform user on stderr of what happened | ||
msg = itpl('\n'+'*'*70+'\n'+self.user_message_template) | ||||
print >> sys.stderr, msg | ||||
fperez
|
r0 | |||
fptest
|
r381 | # Construct report on disk | ||
report.write(self.make_report(traceback)) | ||||
report.close() | ||||
vivainio
|
r917 | raw_input("Press enter to exit:") | ||
fperez
|
r0 | |||
fptest
|
r381 | def make_report(self,traceback): | ||
"""Return a string containing a crash report.""" | ||||
fperez
|
r0 | |||
fptest
|
r381 | sec_sep = '\n\n'+'*'*75+'\n\n' | ||
fperez
|
r0 | |||
fptest
|
r381 | 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) | ||||
Fernando Perez
|
r1858 | rpt_add('BZR revision : %s \n\n' % Release.revision) | ||
fptest
|
r381 | 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 | ||||
fperez
|
r0 | |||
fptest
|
r381 | 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' | ||||
Fernando Perez
|
r1858 | bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' | ||
fptest
|
r381 | 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.""" | ||||
fperez
|
r0 | |||
sec_sep = '\n\n'+'*'*75+'\n\n' | ||||
fptest
|
r381 | |||
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) | ||||
Fernando Perez
|
r1858 | rpt_add('BZR revision : %s \n\n' % Release.revision) | ||
fptest
|
r381 | rpt_add('Platform info : os.name -> %s, sys.platform -> %s' % | ||
fperez
|
r0 | (os.name,sys.platform) ) | ||
fptest
|
r381 | 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) | ||||
fperez
|
r0 | try: | ||
fptest
|
r381 | rpt_add(sec_sep+"History of session input:") | ||
fperez
|
r0 | for line in self.IP.user_ns['_ih']: | ||
fptest
|
r381 | 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') | ||||
fperez
|
r0 | except: | ||
pass | ||||
fptest
|
r381 | |||
return ''.join(report) | ||||