From 737017dea589043226dfef94184c77cccd254a3b 2010-01-31 01:15:53 From: Brian Granger Date: 2010-01-31 01:15:53 Subject: [PATCH] More work on the crash handler. * Made a subclass of :class:`IPython.core.crashhandler.CrashHandler` for each application we have. Each of these has a custom message and uses the name and email of the appropriate personf for that app. * Removed the :meth:`IPython.core.application.abort` method. * Made small changes to how the crash handler is instantiated in :class:`IPython.core.application.Application`. * Tested the crash handlers. --- diff --git a/IPython/core/application.py b/IPython/core/application.py index 1e4940f..8ee1f07 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -119,14 +119,13 @@ class Application(object): argv = None #: extra arguments computed by the command-line loader extra_args = None + #: The class to use as the crash handler. + crash_handler_class = crashhandler.CrashHandler # Private attributes _exiting = False _initialized = False - # Class choices for things that will be instantiated at runtime. - _CrashHandler = crashhandler.CrashHandler - def __init__(self, argv=None): self.argv = sys.argv[1:] if argv is None else argv self.init_logger() @@ -217,7 +216,7 @@ class Application(object): def create_crash_handler(self): """Create a crash handler, typically setting sys.excepthook to it.""" - self.crash_handler = self._CrashHandler(self, self.name) + self.crash_handler = self.crash_handler_class(self) sys.excepthook = self.crash_handler def create_default_config(self): @@ -430,15 +429,6 @@ class Application(object): # Utility methods #------------------------------------------------------------------------- - def abort(self): - """Abort the starting of the application.""" - if self._exiting: - pass - else: - self.log.critical("Aborting application: %s" % self.name, exc_info=True) - self._exiting = True - sys.exit(1) - def exit(self, exit_status=0): if self._exiting: pass @@ -447,17 +437,13 @@ class Application(object): self._exiting = True sys.exit(exit_status) - def attempt(self, func, action='abort'): + def attempt(self, func): try: func() except SystemExit: raise except: - if action == 'abort': - self.log.critical("Aborting application: %s" % self.name, - exc_info=True) - self.abort() - raise - elif action == 'exit': - self.exit(0) + self.log.critical("Aborting application: %s" % self.name, + exc_info=True) + self.exit(0) diff --git a/IPython/core/crashhandler.py b/IPython/core/crashhandler.py index d0fc98f..5c9c3ab 100644 --- a/IPython/core/crashhandler.py +++ b/IPython/core/crashhandler.py @@ -1,124 +1,113 @@ -# -*- coding: utf-8 -*- +# encoding: utf-8 """sys.excepthook for IPython itself, leaves a detailed report on disk. +Authors: -Authors -------- -- Fernando Perez +* Fernando Perez +* Brian E. Granger """ -#***************************************************************************** -# Copyright (C) 2008-2009 The IPython Development Team -# Copyright (C) 2001-2007 Fernando Perez. +#----------------------------------------------------------------------------- +# Copyright (C) 2001-2007 Fernando Perez. +# Copyright (C) 2008-2010 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#***************************************************************************** +#----------------------------------------------------------------------------- -#**************************************************************************** -# Required modules +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- -# From the standard library import os import sys from pprint import pformat -# Our own -from IPython.core import release from IPython.core import ultratb +from IPython.external.Itpl import itpl from IPython.utils.sysinfo import sys_info -from IPython.external.Itpl import itpl +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- -#**************************************************************************** +# Template for the user message. +_default_message_template = """\ +Oops, $self.app_name crashed. We do our best to make it stable, but... -class CrashHandler(object): - """Customizable crash handlers for IPython-based systems. +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. - Instances of this class provide a __call__ method which can be used as a - sys.excepthook, i.e., the __call__ signature is: +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. - def __call__(self,etype, evalue, etb) +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 __init__(self,app, app_name, contact_name=None, contact_email=None, - bug_tracker=None, crash_report_fname='CrashReport.txt', - show_crash_traceback=True, call_pdb=False): - """New crash handler. - Inputs: +class CrashHandler(object): + """Customizable crash handlers for IPython applications. - - app: a running application instance, which will be queried at crash - time for internal information. + Instances of this class provide a :meth:`__call__` method which can be + used as a ``sys.excepthook``. The :meth:`__call__` signature is:: - - app_name: a string containing the name of your application. + def __call__(self, etype, evalue, etb) + """ - - contact_name: a string with the name of the person to contact. + message_template = _default_message_template - - contact_email: a string with the email address of the contact. + def __init__(self, app, contact_name=None, contact_email=None, + bug_tracker=None, show_crash_traceback=True, call_pdb=False): + """Create a new crash handler - - bug_tracker: a string with the URL for your project's bug tracker. + Parameters + ---------- + app : Application + A running :class:`Application` instance, which will be queried at + crash time for internal information. - - 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. + contact_name : str + A string with the name of the person to contact. - Optional inputs: - - - show_crash_traceback(True): if false, don't print the crash - traceback on stderr, only generate the on-disk report + contact_email : str + A string with the email address of the contact. + bug_tracker : str + A string with the URL for your project's bug tracker. + + show_crash_traceback : bool + 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 + 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.app = app - self.app_name = app_name + self.app_name = self.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.crash_report_fname = "Crash_report_%s.txt" % self.app_name self.show_crash_traceback = show_crash_traceback self.section_sep = '\n\n'+'*'*75+'\n\n' self.call_pdb = call_pdb #self.call_pdb = True # dbg - - # 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): + 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) @@ -137,10 +126,11 @@ $self.bug_tracker # 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, - call_pdb=self.call_pdb, - ) + TBhandler = ultratb.VerboseTB( + color_scheme=color_scheme, + long_header=1, + call_pdb=self.call_pdb, + ) if self.call_pdb: TBhandler(etype,evalue,etb) return @@ -159,7 +149,7 @@ $self.bug_tracker return # Inform user on stderr of what happened - msg = itpl('\n'+'*'*70+'\n'+self.user_message_template) + msg = itpl('\n'+'*'*70+'\n'+self.message_template) print >> sys.stderr, msg # Construct report on disk @@ -178,7 +168,9 @@ $self.bug_tracker try: config = pformat(self.app.config) - rpt_add(sec_sep+'Current user configuration structure:\n\n') + rpt_add(sec_sep) + rpt_add('Application name: %s\n\n' % self.app_name) + rpt_add('Current user configuration structure:\n\n') rpt_add(config) except: pass @@ -186,39 +178,3 @@ $self.bug_tracker return ''.join(report) - -class IPythonCrashHandler(CrashHandler): - """sys.excepthook for IPython itself, leaves a detailed report on disk.""" - - def __init__(self, app, app_name='IPython'): - - # Set here which of the IPython authors should be listed as contact - AUTHOR_CONTACT = 'Fernando' - - # Set argument defaults - bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' - contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2] - crash_report_fname = 'IPython_crash_report.txt' - # Call parent constructor - CrashHandler.__init__(self,app,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 = self.section_sep - # Start with parent report - report = [super(IPythonCrashHandler, self).make_report(traceback)] - # Add interactive-specific info we may have - rpt_add = report.append - try: - rpt_add(sec_sep+"History of session input:") - for line in self.app.shell.user_ns['_ih']: - rpt_add(line) - rpt_add('\n*** Last line of input (may not be in above history):\n') - rpt_add(self.app.shell._last_input_line+'\n') - except: - pass - - return ''.join(report) - diff --git a/IPython/core/ipapp.py b/IPython/core/ipapp.py index c30df9b..ea0bba4 100755 --- a/IPython/core/ipapp.py +++ b/IPython/core/ipapp.py @@ -21,13 +21,15 @@ Authors #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- + from __future__ import absolute_import import logging import os import sys -from IPython.core import crashhandler +from IPython.core import release +from IPython.core.crashhandler import CrashHandler from IPython.core.application import Application, BaseAppConfigLoader from IPython.core.iplib import InteractiveShell from IPython.config.loader import ( @@ -317,6 +319,67 @@ class IPAppConfigLoader(BaseAppConfigLoader): #----------------------------------------------------------------------------- +# Crash handler for this application +#----------------------------------------------------------------------------- + + +_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 +""" + +class IPAppCrashHandler(CrashHandler): + """sys.excepthook for IPython itself, leaves a detailed report on disk.""" + + message_template = _message_template + + def __init__(self, app): + contact_name = release.authors['Fernando'][0] + contact_email = release.authors['Fernando'][1] + bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' + super(IPAppCrashHandler,self).__init__( + app, contact_name, contact_email, bug_tracker + ) + + def make_report(self,traceback): + """Return a string containing a crash report.""" + + sec_sep = self.section_sep + # Start with parent report + report = [super(IPAppCrashHandler, self).make_report(traceback)] + # Add interactive-specific info we may have + rpt_add = report.append + try: + rpt_add(sec_sep+"History of session input:") + for line in self.app.shell.user_ns['_ih']: + rpt_add(line) + rpt_add('\n*** Last line of input (may not be in above history):\n') + rpt_add(self.app.shell._last_input_line+'\n') + except: + pass + + return ''.join(report) + + +#----------------------------------------------------------------------------- # Main classes and functions #----------------------------------------------------------------------------- @@ -327,9 +390,7 @@ class IPythonApp(Application): usage = usage.cl_usage command_line_loader = IPAppConfigLoader config_file_name = default_config_file_name - - # Private and configuration attributes - _CrashHandler = crashhandler.IPythonCrashHandler + crash_handler_class = IPAppCrashHandler def create_default_config(self): super(IPythonApp, self).create_default_config() diff --git a/IPython/kernel/clusterdir.py b/IPython/kernel/clusterdir.py index 467c293..84b10e0 100755 --- a/IPython/kernel/clusterdir.py +++ b/IPython/kernel/clusterdir.py @@ -27,6 +27,8 @@ from twisted.python import log from IPython.config.loader import PyFileConfigLoader from IPython.core.application import Application, BaseAppConfigLoader from IPython.core.component import Component +from IPython.core.crashhandler import CrashHandler +from IPython.core import release from IPython.utils.path import ( get_ipython_package_dir, expand_path @@ -290,6 +292,47 @@ class ClusterDirConfigLoader(BaseAppConfigLoader): #----------------------------------------------------------------------------- +# Crash handler for this application +#----------------------------------------------------------------------------- + + +_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. + - 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 +""" + +class ClusterDirCrashHandler(CrashHandler): + """sys.excepthook for IPython itself, leaves a detailed report on disk.""" + + message_template = _message_template + + def __init__(self, app): + contact_name = release.authors['Brian'][0] + contact_email = release.authors['Brian'][1] + bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' + super(ClusterDirCrashHandler,self).__init__( + app, contact_name, contact_email, bug_tracker + ) + + +#----------------------------------------------------------------------------- # Main application #----------------------------------------------------------------------------- @@ -313,6 +356,7 @@ class ApplicationWithClusterDir(Application): """ command_line_loader = ClusterDirConfigLoader + crash_handler_class = ClusterDirCrashHandler auto_create_cluster_dir = True def create_default_config(self):