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