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