electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

base_crash_reporter.py (5160B)


      1 # Electrum - lightweight Bitcoin client
      2 #
      3 # Permission is hereby granted, free of charge, to any person
      4 # obtaining a copy of this software and associated documentation files
      5 # (the "Software"), to deal in the Software without restriction,
      6 # including without limitation the rights to use, copy, modify, merge,
      7 # publish, distribute, sublicense, and/or sell copies of the Software,
      8 # and to permit persons to whom the Software is furnished to do so,
      9 # subject to the following conditions:
     10 #
     11 # The above copyright notice and this permission notice shall be
     12 # included in all copies or substantial portions of the Software.
     13 #
     14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
     18 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
     19 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     20 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     21 # SOFTWARE.
     22 import asyncio
     23 import json
     24 import locale
     25 import traceback
     26 import sys
     27 
     28 from .version import ELECTRUM_VERSION
     29 from . import constants
     30 from .i18n import _
     31 from .util import make_aiohttp_session
     32 from .logging import describe_os_version, Logger, get_git_version
     33 
     34 
     35 class BaseCrashReporter(Logger):
     36     report_server = "https://crashhub.electrum.org"
     37     config_key = "show_crash_reporter"
     38     issue_template = """<h2>Traceback</h2>
     39 <pre>
     40 {traceback}
     41 </pre>
     42 
     43 <h2>Additional information</h2>
     44 <ul>
     45   <li>Electrum version: {app_version}</li>
     46   <li>Python version: {python_version}</li>
     47   <li>Operating system: {os}</li>
     48   <li>Wallet type: {wallet_type}</li>
     49   <li>Locale: {locale}</li>
     50 </ul>
     51     """
     52     CRASH_MESSAGE = _('Something went wrong while executing Electrum.')
     53     CRASH_TITLE = _('Sorry!')
     54     REQUEST_HELP_MESSAGE = _('To help us diagnose and fix the problem, you can send us a bug report that contains '
     55                              'useful debug information:')
     56     DESCRIBE_ERROR_MESSAGE = _("Please briefly describe what led to the error (optional):")
     57     ASK_CONFIRM_SEND = _("Do you want to send this report?")
     58 
     59     def __init__(self, exctype, value, tb):
     60         Logger.__init__(self)
     61         self.exc_args = (exctype, value, tb)
     62 
     63     def send_report(self, asyncio_loop, proxy, endpoint="/crash", *, timeout=None):
     64         if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server:
     65             # Gah! Some kind of altcoin wants to send us crash reports.
     66             raise Exception(_("Missing report URL."))
     67         report = self.get_traceback_info()
     68         report.update(self.get_additional_info())
     69         report = json.dumps(report)
     70         coro = self.do_post(proxy, BaseCrashReporter.report_server + endpoint, data=report)
     71         response = asyncio.run_coroutine_threadsafe(coro, asyncio_loop).result(timeout)
     72         return response
     73 
     74     async def do_post(self, proxy, url, data):
     75         async with make_aiohttp_session(proxy) as session:
     76             async with session.post(url, data=data, raise_for_status=True) as resp:
     77                 return await resp.text()
     78 
     79     def get_traceback_info(self):
     80         exc_string = str(self.exc_args[1])
     81         stack = traceback.extract_tb(self.exc_args[2])
     82         readable_trace = "".join(traceback.format_list(stack))
     83         id = {
     84             "file": stack[-1].filename,
     85             "name": stack[-1].name,
     86             "type": self.exc_args[0].__name__
     87         }
     88         return {
     89             "exc_string": exc_string,
     90             "stack": readable_trace,
     91             "id": id
     92         }
     93 
     94     def get_additional_info(self):
     95         args = {
     96             "app_version": get_git_version() or ELECTRUM_VERSION,
     97             "python_version": sys.version,
     98             "os": describe_os_version(),
     99             "wallet_type": "unknown",
    100             "locale": locale.getdefaultlocale()[0] or "?",
    101             "description": self.get_user_description()
    102         }
    103         try:
    104             args["wallet_type"] = self.get_wallet_type()
    105         except:
    106             # Maybe the wallet isn't loaded yet
    107             pass
    108         return args
    109 
    110     def _get_traceback_str(self) -> str:
    111         return "".join(traceback.format_exception(*self.exc_args))
    112 
    113     def get_report_string(self):
    114         info = self.get_additional_info()
    115         info["traceback"] = self._get_traceback_str()
    116         return self.issue_template.format(**info)
    117 
    118     def get_user_description(self):
    119         raise NotImplementedError
    120 
    121     def get_wallet_type(self) -> str:
    122         raise NotImplementedError
    123 
    124 
    125 def trigger_crash():
    126     # note: do not change the type of the exception, the message,
    127     # or the name of this method. All reports generated through this
    128     # method will be grouped together by the crash reporter, and thus
    129     # don't spam the issue tracker.
    130 
    131     class TestingException(Exception):
    132         pass
    133 
    134     def crash_test():
    135         raise TestingException("triggered crash for testing purposes")
    136 
    137     import threading
    138     t = threading.Thread(target=crash_test)
    139     t.start()