crashhandler.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. # encoding: utf-8
  2. """sys.excepthook for IPython itself, leaves a detailed report on disk.
  3. Authors:
  4. * Fernando Perez
  5. * Brian E. Granger
  6. """
  7. #-----------------------------------------------------------------------------
  8. # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
  9. # Copyright (C) 2008-2011 The IPython Development Team
  10. #
  11. # Distributed under the terms of the BSD License. The full license is in
  12. # the file COPYING, distributed as part of this software.
  13. #-----------------------------------------------------------------------------
  14. #-----------------------------------------------------------------------------
  15. # Imports
  16. #-----------------------------------------------------------------------------
  17. from __future__ import print_function
  18. import os
  19. import sys
  20. import traceback
  21. from pprint import pformat
  22. from IPython.core import ultratb
  23. from IPython.core.release import author_email
  24. from IPython.utils.sysinfo import sys_info
  25. from IPython.utils.py3compat import input, getcwd
  26. #-----------------------------------------------------------------------------
  27. # Code
  28. #-----------------------------------------------------------------------------
  29. # Template for the user message.
  30. _default_message_template = """\
  31. Oops, {app_name} crashed. We do our best to make it stable, but...
  32. A crash report was automatically generated with the following information:
  33. - A verbatim copy of the crash traceback.
  34. - A copy of your input history during this session.
  35. - Data on your current {app_name} configuration.
  36. It was left in the file named:
  37. \t'{crash_report_fname}'
  38. If you can email this file to the developers, the information in it will help
  39. them in understanding and correcting the problem.
  40. You can mail it to: {contact_name} at {contact_email}
  41. with the subject '{app_name} Crash Report'.
  42. If you want to do it now, the following command will work (under Unix):
  43. mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
  44. To ensure accurate tracking of this issue, please file a report about it at:
  45. {bug_tracker}
  46. """
  47. _lite_message_template = """
  48. If you suspect this is an IPython bug, please report it at:
  49. https://github.com/ipython/ipython/issues
  50. or send an email to the mailing list at {email}
  51. You can print a more detailed traceback right now with "%tb", or use "%debug"
  52. to interactively debug it.
  53. Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
  54. {config}Application.verbose_crash=True
  55. """
  56. class CrashHandler(object):
  57. """Customizable crash handlers for IPython applications.
  58. Instances of this class provide a :meth:`__call__` method which can be
  59. used as a ``sys.excepthook``. The :meth:`__call__` signature is::
  60. def __call__(self, etype, evalue, etb)
  61. """
  62. message_template = _default_message_template
  63. section_sep = '\n\n'+'*'*75+'\n\n'
  64. def __init__(self, app, contact_name=None, contact_email=None,
  65. bug_tracker=None, show_crash_traceback=True, call_pdb=False):
  66. """Create a new crash handler
  67. Parameters
  68. ----------
  69. app : Application
  70. A running :class:`Application` instance, which will be queried at
  71. crash time for internal information.
  72. contact_name : str
  73. A string with the name of the person to contact.
  74. contact_email : str
  75. A string with the email address of the contact.
  76. bug_tracker : str
  77. A string with the URL for your project's bug tracker.
  78. show_crash_traceback : bool
  79. If false, don't print the crash traceback on stderr, only generate
  80. the on-disk report
  81. Non-argument instance attributes:
  82. These instances contain some non-argument attributes which allow for
  83. further customization of the crash handler's behavior. Please see the
  84. source for further details.
  85. """
  86. self.crash_report_fname = "Crash_report_%s.txt" % app.name
  87. self.app = app
  88. self.call_pdb = call_pdb
  89. #self.call_pdb = True # dbg
  90. self.show_crash_traceback = show_crash_traceback
  91. self.info = dict(app_name = app.name,
  92. contact_name = contact_name,
  93. contact_email = contact_email,
  94. bug_tracker = bug_tracker,
  95. crash_report_fname = self.crash_report_fname)
  96. def __call__(self, etype, evalue, etb):
  97. """Handle an exception, call for compatible with sys.excepthook"""
  98. # do not allow the crash handler to be called twice without reinstalling it
  99. # this prevents unlikely errors in the crash handling from entering an
  100. # infinite loop.
  101. sys.excepthook = sys.__excepthook__
  102. # Report tracebacks shouldn't use color in general (safer for users)
  103. color_scheme = 'NoColor'
  104. # Use this ONLY for developer debugging (keep commented out for release)
  105. #color_scheme = 'Linux' # dbg
  106. try:
  107. rptdir = self.app.ipython_dir
  108. except:
  109. rptdir = getcwd()
  110. if rptdir is None or not os.path.isdir(rptdir):
  111. rptdir = getcwd()
  112. report_name = os.path.join(rptdir,self.crash_report_fname)
  113. # write the report filename into the instance dict so it can get
  114. # properly expanded out in the user message template
  115. self.crash_report_fname = report_name
  116. self.info['crash_report_fname'] = report_name
  117. TBhandler = ultratb.VerboseTB(
  118. color_scheme=color_scheme,
  119. long_header=1,
  120. call_pdb=self.call_pdb,
  121. )
  122. if self.call_pdb:
  123. TBhandler(etype,evalue,etb)
  124. return
  125. else:
  126. traceback = TBhandler.text(etype,evalue,etb,context=31)
  127. # print traceback to screen
  128. if self.show_crash_traceback:
  129. print(traceback, file=sys.stderr)
  130. # and generate a complete report on disk
  131. try:
  132. report = open(report_name,'w')
  133. except:
  134. print('Could not create crash report on disk.', file=sys.stderr)
  135. return
  136. # Inform user on stderr of what happened
  137. print('\n'+'*'*70+'\n', file=sys.stderr)
  138. print(self.message_template.format(**self.info), file=sys.stderr)
  139. # Construct report on disk
  140. report.write(self.make_report(traceback))
  141. report.close()
  142. input("Hit <Enter> to quit (your terminal may close):")
  143. def make_report(self,traceback):
  144. """Return a string containing a crash report."""
  145. sec_sep = self.section_sep
  146. report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
  147. rpt_add = report.append
  148. rpt_add(sys_info())
  149. try:
  150. config = pformat(self.app.config)
  151. rpt_add(sec_sep)
  152. rpt_add('Application name: %s\n\n' % self.app_name)
  153. rpt_add('Current user configuration structure:\n\n')
  154. rpt_add(config)
  155. except:
  156. pass
  157. rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
  158. return ''.join(report)
  159. def crash_handler_lite(etype, evalue, tb):
  160. """a light excepthook, adding a small message to the usual traceback"""
  161. traceback.print_exception(etype, evalue, tb)
  162. from IPython.core.interactiveshell import InteractiveShell
  163. if InteractiveShell.initialized():
  164. # we are in a Shell environment, give %magic example
  165. config = "%config "
  166. else:
  167. # we are not in a shell, show generic config
  168. config = "c."
  169. print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr)