Show More
@@ -1,220 +1,228 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: 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 |
|
4 | |||
5 | Authors |
|
5 | Authors | |
6 | ------- |
|
6 | ------- | |
7 | - Fernando Perez <Fernando.Perez@berkeley.edu> |
|
7 | - Fernando Perez <Fernando.Perez@berkeley.edu> | |
8 | """ |
|
8 | """ | |
9 |
|
9 | |||
10 | #***************************************************************************** |
|
10 | #***************************************************************************** | |
11 | # Copyright (C) 2008-2009 The IPython Development Team |
|
11 | # Copyright (C) 2008-2009 The IPython Development Team | |
12 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> |
|
12 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> | |
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 | # Required modules |
|
19 | # Required modules | |
20 |
|
20 | |||
21 | # From the standard library |
|
21 | # From the standard library | |
22 | import os |
|
22 | import os | |
23 | import sys |
|
23 | import sys | |
24 | from pprint import pformat |
|
24 | from pprint import pformat | |
25 |
|
25 | |||
26 | # Our own |
|
26 | # Our own | |
27 | from IPython.core import release |
|
27 | from IPython.core import release | |
28 | from IPython.core import ultratb |
|
28 | from IPython.core import ultratb | |
29 | from IPython.external.Itpl import itpl |
|
29 | from IPython.external.Itpl import itpl | |
30 |
|
30 | |||
31 | #**************************************************************************** |
|
31 | #**************************************************************************** | |
32 | class CrashHandler(object): |
|
32 | class CrashHandler(object): | |
33 | """Customizable crash handlers for IPython-based systems. |
|
33 | """Customizable crash handlers for IPython-based systems. | |
34 |
|
34 | |||
35 | Instances of this class provide a __call__ method which can be used as a |
|
35 | Instances of this class provide a __call__ method which can be used as a | |
36 | sys.excepthook, i.e., the __call__ signature is: |
|
36 | sys.excepthook, i.e., the __call__ signature is: | |
37 |
|
37 | |||
38 | def __call__(self,etype, evalue, etb) |
|
38 | def __call__(self,etype, evalue, etb) | |
39 |
|
39 | |||
40 | """ |
|
40 | """ | |
41 |
|
41 | |||
42 | def __init__(self,app, app_name, contact_name=None, contact_email=None, |
|
42 | def __init__(self,app, app_name, contact_name=None, contact_email=None, | |
43 | bug_tracker=None, crash_report_fname='CrashReport.txt', |
|
43 | bug_tracker=None, crash_report_fname='CrashReport.txt', | |
44 | show_crash_traceback=True): |
|
44 | show_crash_traceback=True, call_pdb=False): | |
45 | """New crash handler. |
|
45 | """New crash handler. | |
46 |
|
46 | |||
47 | Inputs: |
|
47 | Inputs: | |
48 |
|
48 | |||
49 | - app: a running application instance, which will be queried at crash |
|
49 | - app: a running application instance, which will be queried at crash | |
50 | time for internal information. |
|
50 | time for internal information. | |
51 |
|
51 | |||
52 | - app_name: a string containing the name of your application. |
|
52 | - app_name: a string containing the name of your application. | |
53 |
|
53 | |||
54 | - contact_name: a string with the name of the person to contact. |
|
54 | - contact_name: a string with the name of the person to contact. | |
55 |
|
55 | |||
56 | - contact_email: a string with the email address of the contact. |
|
56 | - contact_email: a string with the email address of the contact. | |
57 |
|
57 | |||
58 | - bug_tracker: a string with the URL for your project's bug tracker. |
|
58 | - bug_tracker: a string with the URL for your project's bug tracker. | |
59 |
|
59 | |||
60 | - crash_report_fname: a string with the filename for the crash report |
|
60 | - crash_report_fname: a string with the filename for the crash report | |
61 | to be saved in. These reports are left in the ipython user directory |
|
61 | to be saved in. These reports are left in the ipython user directory | |
62 | as determined by the running IPython instance. |
|
62 | as determined by the running IPython instance. | |
63 |
|
63 | |||
64 | Optional inputs: |
|
64 | Optional inputs: | |
65 |
|
65 | |||
66 | - show_crash_traceback(True): if false, don't print the crash |
|
66 | - show_crash_traceback(True): if false, don't print the crash | |
67 | traceback on stderr, only generate the on-disk report |
|
67 | traceback on stderr, only generate the on-disk report | |
68 |
|
68 | |||
69 |
|
69 | |||
70 | Non-argument instance attributes: |
|
70 | Non-argument instance attributes: | |
71 |
|
71 | |||
72 | These instances contain some non-argument attributes which allow for |
|
72 | These instances contain some non-argument attributes which allow for | |
73 | further customization of the crash handler's behavior. Please see the |
|
73 | further customization of the crash handler's behavior. Please see the | |
74 | source for further details. |
|
74 | source for further details. | |
75 | """ |
|
75 | """ | |
76 |
|
76 | |||
77 | # apply args into instance |
|
77 | # apply args into instance | |
78 | self.app = app |
|
78 | self.app = app | |
79 | self.app_name = app_name |
|
79 | self.app_name = app_name | |
80 | self.contact_name = contact_name |
|
80 | self.contact_name = contact_name | |
81 | self.contact_email = contact_email |
|
81 | self.contact_email = contact_email | |
82 | self.bug_tracker = bug_tracker |
|
82 | self.bug_tracker = bug_tracker | |
83 | self.crash_report_fname = crash_report_fname |
|
83 | self.crash_report_fname = crash_report_fname | |
84 | self.show_crash_traceback = show_crash_traceback |
|
84 | self.show_crash_traceback = show_crash_traceback | |
85 | self.section_sep = '\n\n'+'*'*75+'\n\n' |
|
85 | self.section_sep = '\n\n'+'*'*75+'\n\n' | |
|
86 | self.call_pdb = call_pdb | |||
|
87 | #self.call_pdb = True # dbg | |||
86 |
|
88 | |||
87 | # Hardcoded defaults, which can be overridden either by subclasses or |
|
89 | # Hardcoded defaults, which can be overridden either by subclasses or | |
88 | # at runtime for the instance. |
|
90 | # at runtime for the instance. | |
89 |
|
91 | |||
90 | # Template for the user message. Subclasses which completely override |
|
92 | # Template for the user message. Subclasses which completely override | |
91 | # this, or user apps, can modify it to suit their tastes. It gets |
|
93 | # this, or user apps, can modify it to suit their tastes. It gets | |
92 | # expanded using itpl, so calls of the kind $self.foo are valid. |
|
94 | # expanded using itpl, so calls of the kind $self.foo are valid. | |
93 | self.user_message_template = """ |
|
95 | self.user_message_template = """ | |
94 | Oops, $self.app_name crashed. We do our best to make it stable, but... |
|
96 | Oops, $self.app_name crashed. We do our best to make it stable, but... | |
95 |
|
97 | |||
96 | A crash report was automatically generated with the following information: |
|
98 | A crash report was automatically generated with the following information: | |
97 | - A verbatim copy of the crash traceback. |
|
99 | - A verbatim copy of the crash traceback. | |
98 | - A copy of your input history during this session. |
|
100 | - A copy of your input history during this session. | |
99 | - Data on your current $self.app_name configuration. |
|
101 | - Data on your current $self.app_name configuration. | |
100 |
|
102 | |||
101 | It was left in the file named: |
|
103 | It was left in the file named: | |
102 | \t'$self.crash_report_fname' |
|
104 | \t'$self.crash_report_fname' | |
103 | If you can email this file to the developers, the information in it will help |
|
105 | If you can email this file to the developers, the information in it will help | |
104 | them in understanding and correcting the problem. |
|
106 | them in understanding and correcting the problem. | |
105 |
|
107 | |||
106 | You can mail it to: $self.contact_name at $self.contact_email |
|
108 | You can mail it to: $self.contact_name at $self.contact_email | |
107 | with the subject '$self.app_name Crash Report'. |
|
109 | with the subject '$self.app_name Crash Report'. | |
108 |
|
110 | |||
109 | If you want to do it now, the following command will work (under Unix): |
|
111 | If you want to do it now, the following command will work (under Unix): | |
110 | mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname |
|
112 | mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname | |
111 |
|
113 | |||
112 | To ensure accurate tracking of this issue, please file a report about it at: |
|
114 | To ensure accurate tracking of this issue, please file a report about it at: | |
113 | $self.bug_tracker |
|
115 | $self.bug_tracker | |
114 | """ |
|
116 | """ | |
115 |
|
117 | |||
116 | def __call__(self,etype, evalue, etb): |
|
118 | def __call__(self,etype, evalue, etb): | |
117 | """Handle an exception, call for compatible with sys.excepthook""" |
|
119 | """Handle an exception, call for compatible with sys.excepthook""" | |
118 |
|
120 | |||
119 | # Report tracebacks shouldn't use color in general (safer for users) |
|
121 | # Report tracebacks shouldn't use color in general (safer for users) | |
120 | color_scheme = 'NoColor' |
|
122 | color_scheme = 'NoColor' | |
121 |
|
123 | |||
122 | # Use this ONLY for developer debugging (keep commented out for release) |
|
124 | # Use this ONLY for developer debugging (keep commented out for release) | |
123 | #color_scheme = 'Linux' # dbg |
|
125 | #color_scheme = 'Linux' # dbg | |
124 |
|
126 | |||
125 | try: |
|
127 | try: | |
126 | rptdir = self.app.ipython_dir |
|
128 | rptdir = self.app.ipython_dir | |
127 | except: |
|
129 | except: | |
128 | rptdir = os.getcwd() |
|
130 | rptdir = os.getcwd() | |
129 | if not os.path.isdir(rptdir): |
|
131 | if not os.path.isdir(rptdir): | |
130 | rptdir = os.getcwd() |
|
132 | rptdir = os.getcwd() | |
131 | report_name = os.path.join(rptdir,self.crash_report_fname) |
|
133 | report_name = os.path.join(rptdir,self.crash_report_fname) | |
132 | # write the report filename into the instance dict so it can get |
|
134 | # write the report filename into the instance dict so it can get | |
133 | # properly expanded out in the user message template |
|
135 | # properly expanded out in the user message template | |
134 | self.crash_report_fname = report_name |
|
136 | self.crash_report_fname = report_name | |
135 | TBhandler = ultratb.VerboseTB(color_scheme=color_scheme, |
|
137 | TBhandler = ultratb.VerboseTB(color_scheme=color_scheme, | |
136 |
long_header=1 |
|
138 | long_header=1, | |
137 | traceback = TBhandler.text(etype,evalue,etb,context=31) |
|
139 | call_pdb=self.call_pdb, | |
|
140 | ) | |||
|
141 | if self.call_pdb: | |||
|
142 | TBhandler(etype,evalue,etb) | |||
|
143 | return | |||
|
144 | else: | |||
|
145 | traceback = TBhandler.text(etype,evalue,etb,context=31) | |||
138 |
|
146 | |||
139 | # print traceback to screen |
|
147 | # print traceback to screen | |
140 | if self.show_crash_traceback: |
|
148 | if self.show_crash_traceback: | |
141 | print >> sys.stderr, traceback |
|
149 | print >> sys.stderr, traceback | |
142 |
|
150 | |||
143 | # and generate a complete report on disk |
|
151 | # and generate a complete report on disk | |
144 | try: |
|
152 | try: | |
145 | report = open(report_name,'w') |
|
153 | report = open(report_name,'w') | |
146 | except: |
|
154 | except: | |
147 | print >> sys.stderr, 'Could not create crash report on disk.' |
|
155 | print >> sys.stderr, 'Could not create crash report on disk.' | |
148 | return |
|
156 | return | |
149 |
|
157 | |||
150 | # Inform user on stderr of what happened |
|
158 | # Inform user on stderr of what happened | |
151 | msg = itpl('\n'+'*'*70+'\n'+self.user_message_template) |
|
159 | msg = itpl('\n'+'*'*70+'\n'+self.user_message_template) | |
152 | print >> sys.stderr, msg |
|
160 | print >> sys.stderr, msg | |
153 |
|
161 | |||
154 | # Construct report on disk |
|
162 | # Construct report on disk | |
155 | report.write(self.make_report(traceback)) |
|
163 | report.write(self.make_report(traceback)) | |
156 | report.close() |
|
164 | report.close() | |
157 | raw_input("Hit <Enter> to quit this message (your terminal may close):") |
|
165 | raw_input("Hit <Enter> to quit this message (your terminal may close):") | |
158 |
|
166 | |||
159 | def make_report(self,traceback): |
|
167 | def make_report(self,traceback): | |
160 | """Return a string containing a crash report.""" |
|
168 | """Return a string containing a crash report.""" | |
161 | import platform |
|
169 | import platform | |
162 |
|
170 | |||
163 | sec_sep = self.section_sep |
|
171 | sec_sep = self.section_sep | |
164 |
|
172 | |||
165 | report = [] |
|
173 | report = [] | |
166 | rpt_add = report.append |
|
174 | rpt_add = report.append | |
167 |
|
175 | |||
168 | rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') |
|
176 | rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n') | |
169 | rpt_add('IPython version: %s \n' % release.version) |
|
177 | rpt_add('IPython version: %s \n' % release.version) | |
170 | rpt_add('BZR revision : %s \n' % release.revision) |
|
178 | rpt_add('BZR revision : %s \n' % release.revision) | |
171 | rpt_add('Platform info : os.name -> %s, sys.platform -> %s\n' % |
|
179 | rpt_add('Platform info : os.name -> %s, sys.platform -> %s\n' % | |
172 | (os.name,sys.platform) ) |
|
180 | (os.name,sys.platform) ) | |
173 | rpt_add(' : %s\n' % platform.platform()) |
|
181 | rpt_add(' : %s\n' % platform.platform()) | |
174 | rpt_add('Python info : %s\n' % sys.version) |
|
182 | rpt_add('Python info : %s\n' % sys.version) | |
175 |
|
183 | |||
176 | try: |
|
184 | try: | |
177 | config = pformat(self.app.config) |
|
185 | config = pformat(self.app.config) | |
178 | rpt_add(sec_sep+'Current user configuration structure:\n\n') |
|
186 | rpt_add(sec_sep+'Current user configuration structure:\n\n') | |
179 | rpt_add(config) |
|
187 | rpt_add(config) | |
180 | except: |
|
188 | except: | |
181 | pass |
|
189 | pass | |
182 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) |
|
190 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) | |
183 |
|
191 | |||
184 | return ''.join(report) |
|
192 | return ''.join(report) | |
185 |
|
193 | |||
186 |
|
194 | |||
187 | class IPythonCrashHandler(CrashHandler): |
|
195 | class IPythonCrashHandler(CrashHandler): | |
188 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" |
|
196 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" | |
189 |
|
197 | |||
190 | def __init__(self, app, app_name='IPython'): |
|
198 | def __init__(self, app, app_name='IPython'): | |
191 |
|
199 | |||
192 | # Set here which of the IPython authors should be listed as contact |
|
200 | # Set here which of the IPython authors should be listed as contact | |
193 | AUTHOR_CONTACT = 'Fernando' |
|
201 | AUTHOR_CONTACT = 'Fernando' | |
194 |
|
202 | |||
195 | # Set argument defaults |
|
203 | # Set argument defaults | |
196 | bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' |
|
204 | bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug' | |
197 | contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2] |
|
205 | contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2] | |
198 | crash_report_fname = 'IPython_crash_report.txt' |
|
206 | crash_report_fname = 'IPython_crash_report.txt' | |
199 | # Call parent constructor |
|
207 | # Call parent constructor | |
200 | CrashHandler.__init__(self,app,app_name,contact_name,contact_email, |
|
208 | CrashHandler.__init__(self,app,app_name,contact_name,contact_email, | |
201 | bug_tracker,crash_report_fname) |
|
209 | bug_tracker,crash_report_fname) | |
202 |
|
210 | |||
203 | def make_report(self,traceback): |
|
211 | def make_report(self,traceback): | |
204 | """Return a string containing a crash report.""" |
|
212 | """Return a string containing a crash report.""" | |
205 |
|
213 | |||
206 | sec_sep = self.section_sep |
|
214 | sec_sep = self.section_sep | |
207 | # Start with parent report |
|
215 | # Start with parent report | |
208 | report = [super(IPythonCrashHandler, self).make_report(traceback)] |
|
216 | report = [super(IPythonCrashHandler, self).make_report(traceback)] | |
209 | # Add interactive-specific info we may have |
|
217 | # Add interactive-specific info we may have | |
210 | rpt_add = report.append |
|
218 | rpt_add = report.append | |
211 | try: |
|
219 | try: | |
212 | rpt_add(sec_sep+"History of session input:") |
|
220 | rpt_add(sec_sep+"History of session input:") | |
213 | for line in self.app.shell.user_ns['_ih']: |
|
221 | for line in self.app.shell.user_ns['_ih']: | |
214 | rpt_add(line) |
|
222 | rpt_add(line) | |
215 | rpt_add('\n*** Last line of input (may not be in above history):\n') |
|
223 | rpt_add('\n*** Last line of input (may not be in above history):\n') | |
216 | rpt_add(self.app.shell._last_input_line+'\n') |
|
224 | rpt_add(self.app.shell._last_input_line+'\n') | |
217 | except: |
|
225 | except: | |
218 | pass |
|
226 | pass | |
219 |
|
227 | |||
220 | return ''.join(report) |
|
228 | return ''.join(report) |
General Comments 0
You need to be logged in to leave comments.
Login now