| """sys.excepthook for IPython itself, leaves a detailed report on disk. |
| |
| Authors: |
| |
| * Fernando Perez |
| * Brian E. Granger |
| """ |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| import sys |
| import traceback |
| from pprint import pformat |
| from pathlib import Path |
|
|
| import builtins as builtin_mod |
|
|
| from IPython.core import ultratb |
| from IPython.core.application import Application |
| from IPython.core.release import author_email |
| from IPython.utils.sysinfo import sys_info |
|
|
| from IPython.core.release import __version__ as version |
|
|
| from typing import Optional, Dict |
| import types |
|
|
| |
| |
| |
|
|
| |
| _default_message_template = """\ |
| Oops, {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 {app_name} configuration. |
| |
| It was left in the file named: |
| \t'{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: {contact_name} at {contact_email} |
| with the subject '{app_name} Crash Report'. |
| |
| If you want to do it now, the following command will work (under Unix): |
| mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} |
| |
| In your email, please also include information about: |
| - The operating system under which the crash happened: Linux, macOS, Windows, |
| other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2, |
| Windows 10 Pro), and whether it is 32-bit or 64-bit; |
| - How {app_name} was installed: using pip or conda, from GitHub, as part of |
| a Docker container, or other, providing more detail if possible; |
| - How to reproduce the crash: what exact sequence of instructions can one |
| input to get the same crash? Ideally, find a minimal yet complete sequence |
| of instructions that yields the crash. |
| |
| To ensure accurate tracking of this issue, please file a report about it at: |
| {bug_tracker} |
| """ |
|
|
| _lite_message_template = """ |
| If you suspect this is an IPython {version} bug, please report it at: |
| https://github.com/ipython/ipython/issues |
| or send an email to the mailing list at {email} |
| |
| You can print a more detailed traceback right now with "%tb", or use "%debug" |
| to interactively debug it. |
| |
| Extra-detailed tracebacks for bug-reporting purposes can be enabled via: |
| {config}Application.verbose_crash=True |
| """ |
|
|
|
|
| class CrashHandler: |
| """Customizable crash handlers for IPython applications. |
| |
| Instances of this class provide a :meth:`__call__` method which can be |
| used as a ``sys.excepthook``. The :meth:`__call__` signature is:: |
| |
| def __call__(self, etype, evalue, etb) |
| """ |
|
|
| message_template = _default_message_template |
| section_sep = '\n\n'+'*'*75+'\n\n' |
| info: Dict[str, Optional[str]] |
|
|
| def __init__( |
| self, |
| app: Application, |
| contact_name: Optional[str] = None, |
| contact_email: Optional[str] = None, |
| bug_tracker: Optional[str] = None, |
| show_crash_traceback: bool = True, |
| call_pdb: bool = False, |
| ): |
| """Create a new crash handler |
| |
| Parameters |
| ---------- |
| app : Application |
| A running :class:`Application` instance, which will be queried at |
| crash time for internal information. |
| contact_name : str |
| A string with the name of the person to contact. |
| 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 |
| call_pdb |
| Whether to call pdb on crash |
| |
| 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. |
| |
| """ |
| self.crash_report_fname = "Crash_report_%s.txt" % app.name |
| self.app = app |
| self.call_pdb = call_pdb |
| |
| self.show_crash_traceback = show_crash_traceback |
| self.info = dict(app_name = app.name, |
| contact_name = contact_name, |
| contact_email = contact_email, |
| bug_tracker = bug_tracker, |
| crash_report_fname = self.crash_report_fname) |
|
|
| def __call__( |
| self, |
| etype: type[BaseException], |
| evalue: BaseException, |
| etb: types.TracebackType, |
| ) -> None: |
| """Handle an exception, call for compatible with sys.excepthook""" |
|
|
| |
| |
| |
| sys.excepthook = sys.__excepthook__ |
| |
|
|
| |
| ipython_dir = getattr(self.app, "ipython_dir", None) |
| if ipython_dir is not None: |
| assert isinstance(ipython_dir, str) |
| rptdir = Path(ipython_dir) |
| else: |
| rptdir = Path.cwd() |
| if not rptdir.is_dir(): |
| rptdir = Path.cwd() |
| report_name = rptdir / self.crash_report_fname |
| |
| |
| self.crash_report_fname = str(report_name) |
| self.info["crash_report_fname"] = str(report_name) |
| TBhandler = ultratb.VerboseTB( |
| theme_name="nocolor", |
| long_header=True, |
| call_pdb=self.call_pdb, |
| ) |
| if self.call_pdb: |
| TBhandler(etype,evalue,etb) |
| return |
| else: |
| traceback = TBhandler.text(etype,evalue,etb,context=31) |
|
|
| |
| if self.show_crash_traceback: |
| print(traceback, file=sys.stderr) |
|
|
| |
| try: |
| report = open(report_name, "w", encoding="utf-8") |
| except: |
| print('Could not create crash report on disk.', file=sys.stderr) |
| return |
|
|
| with report: |
| |
| print('\n'+'*'*70+'\n', file=sys.stderr) |
| print(self.message_template.format(**self.info), file=sys.stderr) |
|
|
| |
| report.write(self.make_report(str(traceback))) |
|
|
| builtin_mod.input("Hit <Enter> to quit (your terminal may close):") |
|
|
| def make_report(self, traceback: str) -> str: |
| """Return a string containing a crash report.""" |
|
|
| sec_sep = self.section_sep |
|
|
| report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] |
| rpt_add = report.append |
| rpt_add(sys_info()) |
|
|
| try: |
| config = pformat(self.app.config) |
| 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 |
| rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) |
|
|
| return ''.join(report) |
|
|
|
|
| def crash_handler_lite( |
| etype: type[BaseException], evalue: BaseException, tb: types.TracebackType |
| ) -> None: |
| """a light excepthook, adding a small message to the usual traceback""" |
| traceback.print_exception(etype, evalue, tb) |
| |
| from IPython.core.interactiveshell import InteractiveShell |
| if InteractiveShell.initialized(): |
| |
| config = "%config " |
| else: |
| |
| config = "c." |
| print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) |
|
|
|
|