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