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