1 # encoding: utf-8 2 """sys.excepthook for IPython itself, leaves a detailed report on disk. 3 4 Authors: 5 6 * Fernando Perez 7 * Brian E. Granger 8 """ 9 10 #----------------------------------------------------------------------------- 11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> 12 # Copyright (C) 2008-2011 The IPython Development Team 13 # 14 # Distributed under the terms of the BSD License. The full license is in 15 # the file COPYING, distributed as part of this software. 16 #----------------------------------------------------------------------------- 17 18 #----------------------------------------------------------------------------- 19 # Imports 20 #----------------------------------------------------------------------------- 21 22 import sys 23 import traceback 24 from pprint import pformat 25 from pathlib import Path 26 27 import builtins as builtin_mod 28 29 from IPython.core import ultratb 30 from IPython.core.application import Application 31 from IPython.core.release import author_email 32 from IPython.utils.sysinfo import sys_info 33 34 from IPython.core.release import __version__ as version 35 36 from typing import Optional, Dict 37 import types 38 39 #----------------------------------------------------------------------------- 40 # Code 41 #----------------------------------------------------------------------------- 42 43 # Template for the user message. 44 _default_message_template = """\ 45 Oops, {app_name} crashed. We do our best to make it stable, but... 46 47 A crash report was automatically generated with the following information: 48 - A verbatim copy of the crash traceback. 49 - A copy of your input history during this session. 50 - Data on your current {app_name} configuration. 51 52 It was left in the file named: 53 \t'{crash_report_fname}' 54 If you can email this file to the developers, the information in it will help 55 them in understanding and correcting the problem. 56 57 You can mail it to: {contact_name} at {contact_email} 58 with the subject '{app_name} Crash Report'. 59 60 If you want to do it now, the following command will work (under Unix): 61 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} 62 63 In your email, please also include information about: 64 - The operating system under which the crash happened: Linux, macOS, Windows, 65 other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2, 66 Windows 10 Pro), and whether it is 32-bit or 64-bit; 67 - How {app_name} was installed: using pip or conda, from GitHub, as part of 68 a Docker container, or other, providing more detail if possible; 69 - How to reproduce the crash: what exact sequence of instructions can one 70 input to get the same crash? Ideally, find a minimal yet complete sequence 71 of instructions that yields the crash. 72 73 To ensure accurate tracking of this issue, please file a report about it at: 74 {bug_tracker} 75 """ 76 77 _lite_message_template = """ 78 If you suspect this is an IPython {version} bug, please report it at: 79 https://github.com/ipython/ipython/issues 80 or send an email to the mailing list at {email} 81 82 You can print a more detailed traceback right now with "%tb", or use "%debug" 83 to interactively debug it. 84 85 Extra-detailed tracebacks for bug-reporting purposes can be enabled via: 86 {config}Application.verbose_crash=True 87 """ 88 89 90 class CrashHandler: 91 """Customizable crash handlers for IPython applications. 92 93 Instances of this class provide a :meth:`__call__` method which can be 94 used as a ``sys.excepthook``. The :meth:`__call__` signature is:: 95 96 def __call__(self, etype, evalue, etb) 97 """ 98 99 message_template = _default_message_template 100 section_sep = '\n\n'+'*'*75+'\n\n' 101 info: Dict[str, Optional[str]] 102 103 def __init__( 104 self, 105 app: Application, 106 contact_name: Optional[str] = None, 107 contact_email: Optional[str] = None, 108 bug_tracker: Optional[str] = None, 109 show_crash_traceback: bool = True, 110 call_pdb: bool = False, 111 ): 112 """Create a new crash handler 113 114 Parameters 115 ---------- 116 app : Application 117 A running :class:`Application` instance, which will be queried at 118 crash time for internal information. 119 contact_name : str 120 A string with the name of the person to contact. 121 contact_email : str 122 A string with the email address of the contact. 123 bug_tracker : str 124 A string with the URL for your project's bug tracker. 125 show_crash_traceback : bool 126 If false, don't print the crash traceback on stderr, only generate 127 the on-disk report 128 call_pdb 129 Whether to call pdb on crash 130 131 Attributes 132 ---------- 133 These instances contain some non-argument attributes which allow for 134 further customization of the crash handler's behavior. Please see the 135 source for further details. 136 137 """ 138 self.crash_report_fname = "Crash_report_%s.txt" % app.name 139 self.app = app 140 self.call_pdb = call_pdb 141 #self.call_pdb = True # dbg 142 self.show_crash_traceback = show_crash_traceback 143 self.info = dict(app_name = app.name, 144 contact_name = contact_name, 145 contact_email = contact_email, 146 bug_tracker = bug_tracker, 147 crash_report_fname = self.crash_report_fname) 148 149 def __call__( 150 self, 151 etype: type[BaseException], 152 evalue: BaseException, 153 etb: types.TracebackType, 154 ) -> None: 155 """Handle an exception, call for compatible with sys.excepthook""" 156 157 # do not allow the crash handler to be called twice without reinstalling it 158 # this prevents unlikely errors in the crash handling from entering an 159 # infinite loop. 160 sys.excepthook = sys.__excepthook__ 161 162 # Report tracebacks shouldn't use color in general (safer for users) 163 color_scheme = 'NoColor' 164 165 # Use this ONLY for developer debugging (keep commented out for release) 166 # color_scheme = 'Linux' # dbg 167 ipython_dir = getattr(self.app, "ipython_dir", None) 168 if ipython_dir is not None: 169 assert isinstance(ipython_dir, str) 170 rptdir = Path(ipython_dir) 171 else: 172 rptdir = Path.cwd() 173 if not rptdir.is_dir(): 174 rptdir = Path.cwd() 175 report_name = rptdir / self.crash_report_fname 176 # write the report filename into the instance dict so it can get 177 # properly expanded out in the user message template 178 self.crash_report_fname = str(report_name) 179 self.info["crash_report_fname"] = str(report_name) 180 TBhandler = ultratb.VerboseTB( 181 color_scheme=color_scheme, 182 long_header=True, 183 call_pdb=self.call_pdb, 184 ) 185 if self.call_pdb: 186 TBhandler(etype,evalue,etb) 187 return 188 else: 189 traceback = TBhandler.text(etype,evalue,etb,context=31) 190 191 # print traceback to screen 192 if self.show_crash_traceback: 193 print(traceback, file=sys.stderr) 194 195 # and generate a complete report on disk 196 try: 197 report = open(report_name, "w", encoding="utf-8") 198 except: 199 print('Could not create crash report on disk.', file=sys.stderr) 200 return 201 202 with report: 203 # Inform user on stderr of what happened 204 print('\n'+'*'*70+'\n', file=sys.stderr) 205 print(self.message_template.format(**self.info), file=sys.stderr) 206 207 # Construct report on disk 208 report.write(self.make_report(str(traceback))) 209 210 builtin_mod.input("Hit <Enter> to quit (your terminal may close):") 211 212 def make_report(self, traceback: str) -> str: 213 """Return a string containing a crash report.""" 214 215 sec_sep = self.section_sep 216 217 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] 218 rpt_add = report.append 219 rpt_add(sys_info()) 220 221 try: 222 config = pformat(self.app.config) 223 rpt_add(sec_sep) 224 rpt_add("Application name: %s\n\n" % self.app.name) 225 rpt_add("Current user configuration structure:\n\n") 226 rpt_add(config) 227 except: 228 pass 229 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) 230 231 return ''.join(report) 232 233 234 def crash_handler_lite( 235 etype: type[BaseException], evalue: BaseException, tb: types.TracebackType 236 ) -> None: 237 """a light excepthook, adding a small message to the usual traceback""" 238 traceback.print_exception(etype, evalue, tb) 239 240 from IPython.core.interactiveshell import InteractiveShell 241 if InteractiveShell.initialized(): 242 # we are in a Shell environment, give %magic example 243 config = "%config " 244 else: 245 # we are not in a shell, show generic config 246 config = "c." 247 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) 248 249