crashhandler.py
228 lines
| 8.2 KiB
| text/x-python
|
PythonLexer
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 | ||||
Brian Granger
|
r2010 | from pprint import pformat | ||
fperez
|
r0 | |||
Fernando Perez
|
r1875 | # Our own | ||
Brian Granger
|
r2043 | from IPython.core import release | ||
Brian Granger
|
r2048 | from IPython.core import ultratb | ||
Brian Granger
|
r2031 | from IPython.external.Itpl import itpl | ||
Fernando Perez
|
r1875 | |||
fperez
|
r0 | #**************************************************************************** | ||
Fernando Perez
|
r2403 | class CrashHandler(object): | ||
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) | ||||
""" | ||||
Fernando Perez
|
r2403 | def __init__(self,app, app_name, contact_name=None, contact_email=None, | ||
bug_tracker=None, crash_report_fname='CrashReport.txt', | ||||
Fernando Perez
|
r2437 | show_crash_traceback=True, call_pdb=False): | ||
fptest
|
r381 | """New crash handler. | ||
Inputs: | ||||
Fernando Perez
|
r2403 | - app: a running application instance, which will be queried at crash | ||
time for internal information. | ||||
fptest
|
r381 | |||
- 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 | ||||
Fernando Perez
|
r2403 | self.app = app | ||
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 | ||||
Fernando Perez
|
r2403 | self.section_sep = '\n\n'+'*'*75+'\n\n' | ||
Fernando Perez
|
r2437 | self.call_pdb = call_pdb | ||
#self.call_pdb = True # dbg | ||||
fptest
|
r381 | |||
# 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: | ||||
Fernando Perez
|
r2403 | rptdir = self.app.ipython_dir | ||
fperez
|
r0 | 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 | ||||
Brian Granger
|
r2048 | TBhandler = ultratb.VerboseTB(color_scheme=color_scheme, | ||
Fernando Perez
|
r2437 | long_header=1, | ||
call_pdb=self.call_pdb, | ||||
) | ||||
if self.call_pdb: | ||||
TBhandler(etype,evalue,etb) | ||||
return | ||||
else: | ||||
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() | ||||
Fernando Perez
|
r2402 | raw_input("Hit <Enter> to quit this message (your terminal may close):") | ||
fperez
|
r0 | |||
fptest
|
r381 | def make_report(self,traceback): | ||
"""Return a string containing a crash report.""" | ||||
Fernando Perez
|
r2403 | import platform | ||
sec_sep = self.section_sep | ||||
fptest
|
r381 | report = [] | ||
rpt_add = report.append | ||||
rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') | ||||
Fernando Perez
|
r2403 | rpt_add('IPython version: %s \n' % release.version) | ||
rpt_add('BZR revision : %s \n' % release.revision) | ||||
rpt_add('Platform info : os.name -> %s, sys.platform -> %s\n' % | ||||
fptest
|
r381 | (os.name,sys.platform) ) | ||
Fernando Perez
|
r2403 | rpt_add(' : %s\n' % platform.platform()) | ||
rpt_add('Python info : %s\n' % sys.version) | ||||
fptest
|
r381 | try: | ||
Fernando Perez
|
r2403 | config = pformat(self.app.config) | ||
rpt_add(sec_sep+'Current user configuration structure:\n\n') | ||||
rpt_add(config) | ||||
fptest
|
r381 | except: | ||
pass | ||||
Fernando Perez
|
r2403 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) | ||
fperez
|
r0 | |||
fptest
|
r381 | return ''.join(report) | ||
Fernando Perez
|
r2403 | |||
fptest
|
r381 | class IPythonCrashHandler(CrashHandler): | ||
"""sys.excepthook for IPython itself, leaves a detailed report on disk.""" | ||||
Fernando Perez
|
r2403 | def __init__(self, app, app_name='IPython'): | ||
fptest
|
r381 | |||
# Set here which of the IPython authors should be listed as contact | ||||
Fernando Perez
|
r2142 | AUTHOR_CONTACT = 'Fernando' | ||
fptest
|
r381 | |||
# Set argument defaults | ||||
Fernando Perez
|
r1858 | bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' | ||
Brian Granger
|
r2043 | contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2] | ||
fptest
|
r381 | crash_report_fname = 'IPython_crash_report.txt' | ||
# Call parent constructor | ||||
Fernando Perez
|
r2403 | CrashHandler.__init__(self,app,app_name,contact_name,contact_email, | ||
fptest
|
r381 | bug_tracker,crash_report_fname) | ||
def make_report(self,traceback): | ||||
"""Return a string containing a crash report.""" | ||||
fperez
|
r0 | |||
Fernando Perez
|
r2403 | sec_sep = self.section_sep | ||
# Start with parent report | ||||
report = [super(IPythonCrashHandler, self).make_report(traceback)] | ||||
# Add interactive-specific info we may have | ||||
fptest
|
r381 | rpt_add = report.append | ||
fperez
|
r0 | try: | ||
fptest
|
r381 | rpt_add(sec_sep+"History of session input:") | ||
Fernando Perez
|
r2403 | for line in self.app.shell.user_ns['_ih']: | ||
fptest
|
r381 | rpt_add(line) | ||
rpt_add('\n*** Last line of input (may not be in above history):\n') | ||||
Fernando Perez
|
r2403 | rpt_add(self.app.shell._last_input_line+'\n') | ||
fperez
|
r0 | except: | ||
pass | ||||
fptest
|
r381 | |||
return ''.join(report) | ||||