crashhandler.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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. In your email, please also include information about:
  45. - The operating system under which the crash happened: Linux, macOS, Windows,
  46. other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
  47. Windows 10 Pro), and whether it is 32-bit or 64-bit;
  48. - How {app_name} was installed: using pip or conda, from GitHub, as part of
  49. a Docker container, or other, providing more detail if possible;
  50. - How to reproduce the crash: what exact sequence of instructions can one
  51. input to get the same crash? Ideally, find a minimal yet complete sequence
  52. of instructions that yields the crash.
  53. To ensure accurate tracking of this issue, please file a report about it at:
  54. {bug_tracker}
  55. """
  56. _lite_message_template = """
  57. If you suspect this is an IPython bug, please report it at:
  58. https://github.com/ipython/ipython/issues
  59. or send an email to the mailing list at {email}
  60. You can print a more detailed traceback right now with "%tb", or use "%debug"
  61. to interactively debug it.
  62. Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
  63. {config}Application.verbose_crash=True
  64. """
  65. class CrashHandler(object):
  66. """Customizable crash handlers for IPython applications.
  67. Instances of this class provide a :meth:`__call__` method which can be
  68. used as a ``sys.excepthook``. The :meth:`__call__` signature is::
  69. def __call__(self, etype, evalue, etb)
  70. """
  71. message_template = _default_message_template
  72. section_sep = '\n\n'+'*'*75+'\n\n'
  73. def __init__(self, app, contact_name=None, contact_email=None,
  74. bug_tracker=None, show_crash_traceback=True, call_pdb=False):
  75. """Create a new crash handler
  76. Parameters
  77. ----------
  78. app : Application
  79. A running :class:`Application` instance, which will be queried at
  80. crash time for internal information.
  81. contact_name : str
  82. A string with the name of the person to contact.
  83. contact_email : str
  84. A string with the email address of the contact.
  85. bug_tracker : str
  86. A string with the URL for your project's bug tracker.
  87. show_crash_traceback : bool
  88. If false, don't print the crash traceback on stderr, only generate
  89. the on-disk report
  90. Non-argument instance attributes:
  91. These instances contain some non-argument attributes which allow for
  92. further customization of the crash handler's behavior. Please see the
  93. source for further details.
  94. """
  95. self.crash_report_fname = "Crash_report_%s.txt" % app.name
  96. self.app = app
  97. self.call_pdb = call_pdb
  98. #self.call_pdb = True # dbg
  99. self.show_crash_traceback = show_crash_traceback
  100. self.info = dict(app_name = app.name,
  101. contact_name = contact_name,
  102. contact_email = contact_email,
  103. bug_tracker = bug_tracker,
  104. crash_report_fname = self.crash_report_fname)
  105. def __call__(self, etype, evalue, etb):
  106. """Handle an exception, call for compatible with sys.excepthook"""
  107. # do not allow the crash handler to be called twice without reinstalling it
  108. # this prevents unlikely errors in the crash handling from entering an
  109. # infinite loop.
  110. sys.excepthook = sys.__excepthook__
  111. # Report tracebacks shouldn't use color in general (safer for users)
  112. color_scheme = 'NoColor'
  113. # Use this ONLY for developer debugging (keep commented out for release)
  114. #color_scheme = 'Linux' # dbg
  115. try:
  116. rptdir = self.app.ipython_dir
  117. except:
  118. rptdir = getcwd()
  119. if rptdir is None or not os.path.isdir(rptdir):
  120. rptdir = getcwd()
  121. report_name = os.path.join(rptdir,self.crash_report_fname)
  122. # write the report filename into the instance dict so it can get
  123. # properly expanded out in the user message template
  124. self.crash_report_fname = report_name
  125. self.info['crash_report_fname'] = report_name
  126. TBhandler = ultratb.VerboseTB(
  127. color_scheme=color_scheme,
  128. long_header=1,
  129. call_pdb=self.call_pdb,
  130. )
  131. if self.call_pdb:
  132. TBhandler(etype,evalue,etb)
  133. return
  134. else:
  135. traceback = TBhandler.text(etype,evalue,etb,context=31)
  136. # print traceback to screen
  137. if self.show_crash_traceback:
  138. print(traceback, file=sys.stderr)
  139. # and generate a complete report on disk
  140. try:
  141. report = open(report_name,'w')
  142. except:
  143. print('Could not create crash report on disk.', file=sys.stderr)
  144. return
  145. # Inform user on stderr of what happened
  146. print('\n'+'*'*70+'\n', file=sys.stderr)
  147. print(self.message_template.format(**self.info), file=sys.stderr)
  148. # Construct report on disk
  149. report.write(self.make_report(traceback))
  150. report.close()
  151. input("Hit <Enter> to quit (your terminal may close):")
  152. def make_report(self,traceback):
  153. """Return a string containing a crash report."""
  154. sec_sep = self.section_sep
  155. report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
  156. rpt_add = report.append
  157. rpt_add(sys_info())
  158. try:
  159. config = pformat(self.app.config)
  160. rpt_add(sec_sep)
  161. rpt_add('Application name: %s\n\n' % self.app_name)
  162. rpt_add('Current user configuration structure:\n\n')
  163. rpt_add(config)
  164. except:
  165. pass
  166. rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
  167. return ''.join(report)
  168. def crash_handler_lite(etype, evalue, tb):
  169. """a light excepthook, adding a small message to the usual traceback"""
  170. traceback.print_exception(etype, evalue, tb)
  171. from IPython.core.interactiveshell import InteractiveShell
  172. if InteractiveShell.initialized():
  173. # we are in a Shell environment, give %magic example
  174. config = "%config "
  175. else:
  176. # we are not in a shell, show generic config
  177. config = "c."
  178. print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr)