electrum

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

commit 34080187ffb956ceb28100130c3f70716e7f4979
parent 6faef7efe34393e91e33afdfba0746f9794ddb4d
Author: ThomasV <thomasv@electrum.org>
Date:   Tue, 30 Jan 2018 11:16:42 +0100

Merge pull request #2339 from bauerj/error-window

Semi-automated crash reporting
Diffstat:
Melectrum | 7+++++--
Agui/qt/exception_window.py | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgui/qt/main_window.py | 11++++++++++-
Mlib/util.py | 27+++++++++++++++++++++++++++
4 files changed, 223 insertions(+), 3 deletions(-)

diff --git a/electrum b/electrum @@ -87,7 +87,8 @@ if is_local or is_android: imp.load_module('electrum_plugins', *imp.find_module('plugins')) -from electrum import bitcoin + +from electrum import bitcoin, util from electrum import SimpleConfig, Network from electrum.wallet import Wallet, Imported_Wallet from electrum.storage import WalletStorage @@ -296,8 +297,10 @@ def init_plugins(config, gui_name): from electrum.plugins import Plugins return Plugins(config, is_local or is_android, gui_name) -if __name__ == '__main__': +if __name__ == '__main__': + # The hook will only be used in the Qt GUI right now + util.setup_thread_excepthook() # on osx, delete Process Serial Number arg generated for apps launched in Finder sys.argv = list(filter(lambda x: not x.startswith('-psn'), sys.argv)) diff --git a/gui/qt/exception_window.py b/gui/qt/exception_window.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import json +import locale +import platform +import traceback + +import requests +from PyQt5.QtCore import QObject +import PyQt5.QtCore as QtCore +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import * + +from electrum.i18n import _ +import sys +from electrum import ELECTRUM_VERSION + +issue_template = """<h2>Traceback</h2> +<pre> +{traceback} +</pre> + +<h2>Additional information</h2> +<ul> + <li>Electrum version: {electrum_version}</li> + <li>Operating system: {os}</li> + <li>Wallet type: {wallet_type}</li> + <li>Locale: {locale}</li> +</ul> +""" +report_server = "https://crashhub.electrum.org/crash" + + +class Exception_Window(QWidget): + _active_window = None + + def __init__(self, main_window, exctype, value, tb): + self.exc_args = (exctype, value, tb) + self.main_window = main_window + QWidget.__init__(self) + self.setWindowTitle('Electrum - ' + _('An Error Occured')) + self.setMinimumSize(600, 300) + + main_box = QVBoxLayout() + + heading = QLabel('<h2>' + _('Sorry!') + '</h2>') + main_box.addWidget(heading) + main_box.addWidget(QLabel(_('Something went wrong while executing Electrum.'))) + + main_box.addWidget(QLabel( + _('To help us diagnose and fix the problem, you can send us a bug report that contains useful debug ' + 'information:'))) + + collapse_info = QPushButton(_("Show report contents")) + collapse_info.clicked.connect(lambda: QMessageBox.about(self, "Report contents", self.get_report_string())) + main_box.addWidget(collapse_info) + + main_box.addWidget(QLabel(_("Please briefly describe what led to the error (optional):"))) + + self.description_textfield = QTextEdit() + self.description_textfield.setFixedHeight(50) + main_box.addWidget(self.description_textfield) + + main_box.addWidget(QLabel(_("Do you want to send this report?"))) + + buttons = QHBoxLayout() + + report_button = QPushButton(_('Send Bug Report')) + report_button.clicked.connect(self.send_report) + report_button.setIcon(QIcon(":icons/tab_send.png")) + buttons.addWidget(report_button) + + never_button = QPushButton(_('Never')) + never_button.clicked.connect(self.show_never) + buttons.addWidget(never_button) + + close_button = QPushButton(_('Not Now')) + close_button.clicked.connect(self.close) + buttons.addWidget(close_button) + + main_box.addLayout(buttons) + + self.setLayout(main_box) + self.show() + + def send_report(self): + report = self.get_traceback_info() + report.update(self.get_additional_info()) + report = json.dumps(report) + response = requests.post(report_server, data=report) + QMessageBox.about(self, "Crash report", response.text) + self.close() + + def on_close(self): + Exception_Window._active_window = None + sys.__excepthook__(*self.exc_args) + self.close() + + def show_never(self): + self.main_window.config.set_key("show_crash_reporter", False) + self.close() + + def closeEvent(self, event): + self.on_close() + event.accept() + + def get_traceback_info(self): + exc_string = str(self.exc_args[1]) + stack = traceback.extract_tb(self.exc_args[2]) + readable_trace = "".join(traceback.format_list(stack)) + id = { + "file": stack[-1].filename, + "name": stack[-1].name, + "type": self.exc_args[0].__name__ + } + return { + "exc_string": exc_string, + "stack": readable_trace, + "id": id + } + + def get_additional_info(self): + args = { + "electrum_version": ELECTRUM_VERSION, + "os": platform.platform(), + "wallet_type": "unknown", + "locale": locale.getdefaultlocale()[0], + "description": self.description_textfield.toPlainText() + } + try: + args["wallet_type"] = self.main_window.wallet.wallet_type + except: + # Maybe the wallet isn't loaded yet + pass + return args + + def get_report_string(self): + info = self.get_additional_info() + info["traceback"] = "".join(traceback.format_exception(*self.exc_args)) + return issue_template.format(**info) + + +def _show_window(*args): + if not Exception_Window._active_window: + Exception_Window._active_window = Exception_Window(*args) + + +class Exception_Hook(QObject): + _report_exception = QtCore.pyqtSignal(object, object, object, object) + + def __init__(self, main_window, *args, **kwargs): + super(Exception_Hook, self).__init__(*args, **kwargs) + if not main_window.config.get("show_crash_reporter", default=True): + return + self.main_window = main_window + sys.excepthook = self.handler + self._report_exception.connect(_show_window) + + def handler(self, *args): + self._report_exception.emit(self.main_window, *args) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -32,8 +32,11 @@ from decimal import Decimal import base64 from functools import partial -from PyQt5.QtCore import Qt from PyQt5.QtGui import * +from PyQt5.QtCore import * +import PyQt5.QtCore as QtCore + +from .exception_window import Exception_Hook from PyQt5.QtWidgets import * from electrum.util import bh2u, bfh @@ -103,6 +106,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.gui_object = gui_object self.config = config = gui_object.config + + self.setup_exception_hook() + self.network = gui_object.daemon.network self.fx = gui_object.daemon.fx self.invoices = wallet.invoices @@ -204,6 +210,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def on_history(self, b): self.new_fx_history_signal.emit() + def setup_exception_hook(self): + Exception_Hook(self) + def on_fx_history(self): self.history_list.refresh_headers() self.history_list.update() diff --git a/lib/util.py b/lib/util.py @@ -707,3 +707,29 @@ class QueuePipe: self.send(request) + + +def setup_thread_excepthook(): + """ + Workaround for `sys.excepthook` thread bug from: + http://bugs.python.org/issue1230540 + + Call once from the main thread before creating any threads. + """ + + init_original = threading.Thread.__init__ + + def init(self, *args, **kwargs): + + init_original(self, *args, **kwargs) + run_original = self.run + + def run_with_except_hook(*args2, **kwargs2): + try: + run_original(*args2, **kwargs2) + except Exception: + sys.excepthook(*sys.exc_info()) + + self.run = run_with_except_hook + + threading.Thread.__init__ = init+ \ No newline at end of file