##// END OF EJS Templates
Don't use crash_handler by default...
MinRK -
Show More
@@ -1,319 +1,336 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15 * Min RK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008-2011 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 import atexit
31 31 import glob
32 32 import logging
33 33 import os
34 34 import shutil
35 35 import sys
36 36
37 37 from IPython.config.application import Application, catch_config_error
38 38 from IPython.config.configurable import Configurable
39 39 from IPython.config.loader import Config, ConfigFileNotFound
40 40 from IPython.core import release, crashhandler
41 41 from IPython.core.profiledir import ProfileDir, ProfileDirError
42 42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
43 43 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
44 44 from IPython.utils import py3compat
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Classes and functions
48 48 #-----------------------------------------------------------------------------
49 49
50 50
51 51 #-----------------------------------------------------------------------------
52 52 # Base Application Class
53 53 #-----------------------------------------------------------------------------
54 54
55 55 # aliases and flags
56 56
57 57 base_aliases = {
58 58 'profile' : 'BaseIPythonApplication.profile',
59 59 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
60 60 'log-level' : 'Application.log_level',
61 61 }
62 62
63 63 base_flags = dict(
64 64 debug = ({'Application' : {'log_level' : logging.DEBUG}},
65 65 "set log level to logging.DEBUG (maximize logging output)"),
66 66 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
67 67 "set log level to logging.CRITICAL (minimize logging output)"),
68 68 init = ({'BaseIPythonApplication' : {
69 69 'copy_config_files' : True,
70 70 'auto_create' : True}
71 71 }, """Initialize profile with default config files. This is equivalent
72 72 to running `ipython profile create <profile>` prior to startup.
73 73 """)
74 74 )
75 75
76 76
77 77 class BaseIPythonApplication(Application):
78 78
79 79 name = Unicode(u'ipython')
80 80 description = Unicode(u'IPython: an enhanced interactive Python shell.')
81 81 version = Unicode(release.version)
82 82
83 83 aliases = Dict(base_aliases)
84 84 flags = Dict(base_flags)
85 85 classes = List([ProfileDir])
86 86
87 87 # Track whether the config_file has changed,
88 88 # because some logic happens only if we aren't using the default.
89 89 config_file_specified = Bool(False)
90 90
91 91 config_file_name = Unicode(u'ipython_config.py')
92 92 def _config_file_name_default(self):
93 93 return self.name.replace('-','_') + u'_config.py'
94 94 def _config_file_name_changed(self, name, old, new):
95 95 if new != old:
96 96 self.config_file_specified = True
97 97
98 98 # The directory that contains IPython's builtin profiles.
99 99 builtin_profile_dir = Unicode(
100 100 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
101 101 )
102 102
103 103 config_file_paths = List(Unicode)
104 104 def _config_file_paths_default(self):
105 105 return [os.getcwdu()]
106 106
107 107 profile = Unicode(u'default', config=True,
108 108 help="""The IPython profile to use."""
109 109 )
110 110
111 111 def _profile_changed(self, name, old, new):
112 112 self.builtin_profile_dir = os.path.join(
113 113 get_ipython_package_dir(), u'config', u'profile', new
114 114 )
115 115
116 116 ipython_dir = Unicode(get_ipython_dir(), config=True,
117 117 help="""
118 118 The name of the IPython directory. This directory is used for logging
119 119 configuration (through profiles), history storage, etc. The default
120 120 is usually $HOME/.ipython. This options can also be specified through
121 121 the environment variable IPYTHON_DIR.
122 122 """
123 123 )
124 124
125 125 overwrite = Bool(False, config=True,
126 126 help="""Whether to overwrite existing config files when copying""")
127 127 auto_create = Bool(False, config=True,
128 128 help="""Whether to create profile dir if it doesn't exist""")
129 129
130 130 config_files = List(Unicode)
131 131 def _config_files_default(self):
132 132 return [u'ipython_config.py']
133 133
134 134 copy_config_files = Bool(False, config=True,
135 135 help="""Whether to install the default config files into the profile dir.
136 136 If a new profile is being created, and IPython contains config files for that
137 137 profile, then they will be staged into the new directory. Otherwise,
138 138 default config files will be automatically generated.
139 139 """)
140
141 verbose_crash = Bool(False, config=True,
142 help="""Create a massive crash report when IPython enconters what may be an
143 internal error. The default is to append a short message to the
144 usual traceback""")
140 145
141 146 # The class to use as the crash handler.
142 147 crash_handler_class = Type(crashhandler.CrashHandler)
143 148
144 149 def __init__(self, **kwargs):
145 150 super(BaseIPythonApplication, self).__init__(**kwargs)
146 151 # ensure even default IPYTHON_DIR exists
147 152 if not os.path.exists(self.ipython_dir):
148 153 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
149 154
150 155 #-------------------------------------------------------------------------
151 156 # Various stages of Application creation
152 157 #-------------------------------------------------------------------------
153 158
154 159 def init_crash_handler(self):
155 160 """Create a crash handler, typically setting sys.excepthook to it."""
156 161 self.crash_handler = self.crash_handler_class(self)
157 sys.excepthook = self.crash_handler
162 sys.excepthook = self.excepthook
158 163 def unset_crashhandler():
159 164 sys.excepthook = sys.__excepthook__
160 165 atexit.register(unset_crashhandler)
161
166
167 def excepthook(self, etype, evalue, tb):
168 """this is sys.excepthook after init_crashhandler
169
170 set self.verbose_crash=True to use our full crashhandler, instead of
171 a regular traceback with a short message (crash_handler_lite)
172 """
173
174 if self.verbose_crash:
175 return self.crash_handler(etype, evalue, tb)
176 else:
177 return crashhandler.crash_handler_lite(etype, evalue, tb)
178
162 179 def _ipython_dir_changed(self, name, old, new):
163 180 if old in sys.path:
164 181 sys.path.remove(old)
165 182 sys.path.append(os.path.abspath(new))
166 183 if not os.path.isdir(new):
167 184 os.makedirs(new, mode=0777)
168 185 readme = os.path.join(new, 'README')
169 186 if not os.path.exists(readme):
170 187 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
171 188 shutil.copy(os.path.join(path, 'README'), readme)
172 189 self.log.debug("IPYTHON_DIR set to: %s" % new)
173 190
174 191 def load_config_file(self, suppress_errors=True):
175 192 """Load the config file.
176 193
177 194 By default, errors in loading config are handled, and a warning
178 195 printed on screen. For testing, the suppress_errors option is set
179 196 to False, so errors will make tests fail.
180 197 """
181 198 self.log.debug("Searching path %s for config files", self.config_file_paths)
182 199 base_config = 'ipython_config.py'
183 200 self.log.debug("Attempting to load config file: %s" %
184 201 base_config)
185 202 try:
186 203 Application.load_config_file(
187 204 self,
188 205 base_config,
189 206 path=self.config_file_paths
190 207 )
191 208 except ConfigFileNotFound:
192 209 # ignore errors loading parent
193 210 self.log.debug("Config file %s not found", base_config)
194 211 pass
195 212 if self.config_file_name == base_config:
196 213 # don't load secondary config
197 214 return
198 215 self.log.debug("Attempting to load config file: %s" %
199 216 self.config_file_name)
200 217 try:
201 218 Application.load_config_file(
202 219 self,
203 220 self.config_file_name,
204 221 path=self.config_file_paths
205 222 )
206 223 except ConfigFileNotFound:
207 224 # Only warn if the default config file was NOT being used.
208 225 if self.config_file_specified:
209 226 msg = self.log.warn
210 227 else:
211 228 msg = self.log.debug
212 229 msg("Config file not found, skipping: %s", self.config_file_name)
213 230 except:
214 231 # For testing purposes.
215 232 if not suppress_errors:
216 233 raise
217 234 self.log.warn("Error loading config file: %s" %
218 235 self.config_file_name, exc_info=True)
219 236
220 237 def init_profile_dir(self):
221 238 """initialize the profile dir"""
222 239 try:
223 240 # location explicitly specified:
224 241 location = self.config.ProfileDir.location
225 242 except AttributeError:
226 243 # location not specified, find by profile name
227 244 try:
228 245 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
229 246 except ProfileDirError:
230 247 # not found, maybe create it (always create default profile)
231 248 if self.auto_create or self.profile=='default':
232 249 try:
233 250 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
234 251 except ProfileDirError:
235 252 self.log.fatal("Could not create profile: %r"%self.profile)
236 253 self.exit(1)
237 254 else:
238 255 self.log.info("Created profile dir: %r"%p.location)
239 256 else:
240 257 self.log.fatal("Profile %r not found."%self.profile)
241 258 self.exit(1)
242 259 else:
243 260 self.log.info("Using existing profile dir: %r"%p.location)
244 261 else:
245 262 # location is fully specified
246 263 try:
247 264 p = ProfileDir.find_profile_dir(location, self.config)
248 265 except ProfileDirError:
249 266 # not found, maybe create it
250 267 if self.auto_create:
251 268 try:
252 269 p = ProfileDir.create_profile_dir(location, self.config)
253 270 except ProfileDirError:
254 271 self.log.fatal("Could not create profile directory: %r"%location)
255 272 self.exit(1)
256 273 else:
257 274 self.log.info("Creating new profile dir: %r"%location)
258 275 else:
259 276 self.log.fatal("Profile directory %r not found."%location)
260 277 self.exit(1)
261 278 else:
262 279 self.log.info("Using existing profile dir: %r"%location)
263 280
264 281 self.profile_dir = p
265 282 self.config_file_paths.append(p.location)
266 283
267 284 def init_config_files(self):
268 285 """[optionally] copy default config files into profile dir."""
269 286 # copy config files
270 287 path = self.builtin_profile_dir
271 288 if self.copy_config_files:
272 289 src = self.profile
273 290
274 291 cfg = self.config_file_name
275 292 if path and os.path.exists(os.path.join(path, cfg)):
276 293 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
277 294 cfg, src, self.profile_dir.location, self.overwrite)
278 295 )
279 296 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
280 297 else:
281 298 self.stage_default_config_file()
282 299 else:
283 300 # Still stage *bundled* config files, but not generated ones
284 301 # This is necessary for `ipython profile=sympy` to load the profile
285 302 # on the first go
286 303 files = glob.glob(os.path.join(path, '*.py'))
287 304 for fullpath in files:
288 305 cfg = os.path.basename(fullpath)
289 306 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
290 307 # file was copied
291 308 self.log.warn("Staging bundled %s from %s into %r"%(
292 309 cfg, self.profile, self.profile_dir.location)
293 310 )
294 311
295 312
296 313 def stage_default_config_file(self):
297 314 """auto generate default config file, and stage it into the profile."""
298 315 s = self.generate_config_file()
299 316 fname = os.path.join(self.profile_dir.location, self.config_file_name)
300 317 if self.overwrite or not os.path.exists(fname):
301 318 self.log.warn("Generating default config file: %r"%(fname))
302 319 with open(fname, 'w') as f:
303 320 f.write(s)
304 321
305 322 @catch_config_error
306 323 def initialize(self, argv=None):
307 324 # don't hook up crash handler before parsing command-line
308 325 self.parse_command_line(argv)
309 326 self.init_crash_handler()
310 327 if self.subapp is not None:
311 328 # stop here if subapp is taking over
312 329 return
313 330 cl_config = self.config
314 331 self.init_profile_dir()
315 332 self.init_config_files()
316 333 self.load_config_file()
317 334 # enforce cl-opts override configfile opts:
318 335 self.update_config(cl_config)
319 336
@@ -1,186 +1,211 b''
1 1 # encoding: utf-8
2 2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez
7 7 * Brian E. Granger
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 12 # Copyright (C) 2008-2010 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import os
23 23 import sys
24 import traceback
24 25 from pprint import pformat
25 26
26 27 from IPython.core import ultratb
28 from IPython.core.release import author_email
27 29 from IPython.utils.sysinfo import sys_info
28 30
29 31 #-----------------------------------------------------------------------------
30 32 # Code
31 33 #-----------------------------------------------------------------------------
32 34
33 35 # Template for the user message.
34 36 _default_message_template = """\
35 37 Oops, {app_name} crashed. We do our best to make it stable, but...
36 38
37 39 A crash report was automatically generated with the following information:
38 40 - A verbatim copy of the crash traceback.
39 41 - A copy of your input history during this session.
40 42 - Data on your current {app_name} configuration.
41 43
42 44 It was left in the file named:
43 45 \t'{crash_report_fname}'
44 46 If you can email this file to the developers, the information in it will help
45 47 them in understanding and correcting the problem.
46 48
47 49 You can mail it to: {contact_name} at {contact_email}
48 50 with the subject '{app_name} Crash Report'.
49 51
50 52 If you want to do it now, the following command will work (under Unix):
51 53 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
52 54
53 55 To ensure accurate tracking of this issue, please file a report about it at:
54 56 {bug_tracker}
55 57 """
56 58
59 _lite_message_template = """
60 If you suspect this is an IPython bug, please report it at:
61 https://github.com/ipython/ipython/issues
62 or send an email to the mailing list at {email}
63
64 You can enable a much more verbose traceback with:
65 {config}Application.verbose_crash=True
66 """
67
57 68
58 69 class CrashHandler(object):
59 70 """Customizable crash handlers for IPython applications.
60 71
61 72 Instances of this class provide a :meth:`__call__` method which can be
62 73 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
63 74
64 75 def __call__(self, etype, evalue, etb)
65 76 """
66 77
67 78 message_template = _default_message_template
68 79 section_sep = '\n\n'+'*'*75+'\n\n'
69 80
70 81 def __init__(self, app, contact_name=None, contact_email=None,
71 82 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
72 83 """Create a new crash handler
73 84
74 85 Parameters
75 86 ----------
76 87 app : Application
77 88 A running :class:`Application` instance, which will be queried at
78 89 crash time for internal information.
79 90
80 91 contact_name : str
81 92 A string with the name of the person to contact.
82 93
83 94 contact_email : str
84 95 A string with the email address of the contact.
85 96
86 97 bug_tracker : str
87 98 A string with the URL for your project's bug tracker.
88 99
89 100 show_crash_traceback : bool
90 101 If false, don't print the crash traceback on stderr, only generate
91 102 the on-disk report
92 103
93 104 Non-argument instance attributes:
94 105
95 106 These instances contain some non-argument attributes which allow for
96 107 further customization of the crash handler's behavior. Please see the
97 108 source for further details.
98 109 """
99 110 self.crash_report_fname = "Crash_report_%s.txt" % app.name
100 111 self.app = app
101 112 self.call_pdb = call_pdb
102 113 #self.call_pdb = True # dbg
103 114 self.show_crash_traceback = show_crash_traceback
104 115 self.info = dict(app_name = app.name,
105 116 contact_name = contact_name,
106 117 contact_email = contact_email,
107 118 bug_tracker = bug_tracker,
108 119 crash_report_fname = self.crash_report_fname)
109 120
110 121
111 122 def __call__(self, etype, evalue, etb):
112 123 """Handle an exception, call for compatible with sys.excepthook"""
113 124
114 125 # do not allow the crash handler to be called twice without reinstalling it
115 126 # this prevents unlikely errors in the crash handling from entering an
116 127 # infinite loop.
117 128 sys.excepthook = sys.__excepthook__
118 129
119 130 # Report tracebacks shouldn't use color in general (safer for users)
120 131 color_scheme = 'NoColor'
121 132
122 133 # Use this ONLY for developer debugging (keep commented out for release)
123 134 #color_scheme = 'Linux' # dbg
124 135 try:
125 136 rptdir = self.app.ipython_dir
126 137 except:
127 138 rptdir = os.getcwdu()
128 139 if rptdir is None or not os.path.isdir(rptdir):
129 140 rptdir = os.getcwdu()
130 141 report_name = os.path.join(rptdir,self.crash_report_fname)
131 142 # write the report filename into the instance dict so it can get
132 143 # properly expanded out in the user message template
133 144 self.crash_report_fname = report_name
134 145 self.info['crash_report_fname'] = report_name
135 146 TBhandler = ultratb.VerboseTB(
136 147 color_scheme=color_scheme,
137 148 long_header=1,
138 149 call_pdb=self.call_pdb,
139 150 )
140 151 if self.call_pdb:
141 152 TBhandler(etype,evalue,etb)
142 153 return
143 154 else:
144 155 traceback = TBhandler.text(etype,evalue,etb,context=31)
145 156
146 157 # print traceback to screen
147 158 if self.show_crash_traceback:
148 159 print >> sys.stderr, traceback
149 160
150 161 # and generate a complete report on disk
151 162 try:
152 163 report = open(report_name,'w')
153 164 except:
154 165 print >> sys.stderr, 'Could not create crash report on disk.'
155 166 return
156 167
157 168 # Inform user on stderr of what happened
158 169 print >> sys.stderr, '\n'+'*'*70+'\n'
159 170 print >> sys.stderr, self.message_template.format(**self.info)
160 171
161 172 # Construct report on disk
162 173 report.write(self.make_report(traceback))
163 174 report.close()
164 raw_input("Hit <Enter> to quit this message (your terminal may close):")
175 raw_input("Hit <Enter> to quit (your terminal may close):")
165 176
166 177 def make_report(self,traceback):
167 178 """Return a string containing a crash report."""
168 179
169 180 sec_sep = self.section_sep
170 181
171 182 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
172 183 rpt_add = report.append
173 184 rpt_add(sys_info())
174 185
175 186 try:
176 187 config = pformat(self.app.config)
177 188 rpt_add(sec_sep)
178 189 rpt_add('Application name: %s\n\n' % self.app_name)
179 190 rpt_add('Current user configuration structure:\n\n')
180 191 rpt_add(config)
181 192 except:
182 193 pass
183 194 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
184 195
185 196 return ''.join(report)
186 197
198
199 def crash_handler_lite(etype, evalue, tb):
200 """a light excepthook, adding a small message to the usual traceback"""
201 traceback.print_exception(etype, evalue, tb)
202
203 from IPython.core.interactiveshell import InteractiveShell
204 if InteractiveShell.initialized():
205 # we are in a Shell environment, give %magic example
206 config = "%config "
207 else:
208 # we are not in a shell, show generic config
209 config = "c."
210 print >> sys.stderr, _lite_message_template.format(email=author_email, config=config)
211
General Comments 0
You need to be logged in to leave comments. Login now