Browse Source
* Split crash reporter class In Qt related stuff and basic stuff. * Crash reports from Android * Ignore exceptions in crash_reporter (if any) * Open issue in browser * Switch back to real servermaster
4 changed files with 342 additions and 91 deletions
@ -0,0 +1,193 @@ |
|||||||
|
import sys |
||||||
|
|
||||||
|
import requests |
||||||
|
from kivy import base, utils |
||||||
|
from kivy.clock import Clock |
||||||
|
from kivy.core.window import Window |
||||||
|
from kivy.factory import Factory |
||||||
|
from kivy.lang import Builder |
||||||
|
from kivy.uix.label import Label |
||||||
|
from kivy.utils import platform |
||||||
|
|
||||||
|
|
||||||
|
from electrum.base_crash_reporter import BaseCrashReporter |
||||||
|
from electrum.i18n import _ |
||||||
|
|
||||||
|
|
||||||
|
Builder.load_string(''' |
||||||
|
<CrashReporter@Popup> |
||||||
|
BoxLayout: |
||||||
|
orientation: 'vertical' |
||||||
|
Label: |
||||||
|
id: crash_message |
||||||
|
text_size: root.width, None |
||||||
|
size: self.texture_size |
||||||
|
size_hint: None, None |
||||||
|
Label: |
||||||
|
id: request_help_message |
||||||
|
text_size: root.width*.95, None |
||||||
|
size: self.texture_size |
||||||
|
size_hint: None, None |
||||||
|
BoxLayout: |
||||||
|
size_hint: 1, 0.1 |
||||||
|
Button: |
||||||
|
text: 'Show report contents' |
||||||
|
height: '48dp' |
||||||
|
size_hint: 1, None |
||||||
|
on_press: root.show_contents() |
||||||
|
BoxLayout: |
||||||
|
size_hint: 1, 0.1 |
||||||
|
Label: |
||||||
|
id: describe_error_message |
||||||
|
text_size: root.width, None |
||||||
|
size: self.texture_size |
||||||
|
size_hint: None, None |
||||||
|
TextInput: |
||||||
|
id: user_message |
||||||
|
size_hint: 1, 0.3 |
||||||
|
BoxLayout: |
||||||
|
size_hint: 1, 0.7 |
||||||
|
BoxLayout: |
||||||
|
size_hint: 1, None |
||||||
|
height: '48dp' |
||||||
|
orientation: 'horizontal' |
||||||
|
Button: |
||||||
|
height: '48dp' |
||||||
|
text: 'Send' |
||||||
|
on_release: root.send_report() |
||||||
|
Button: |
||||||
|
text: 'Never' |
||||||
|
on_release: root.show_never() |
||||||
|
Button: |
||||||
|
text: 'Not now' |
||||||
|
on_release: root.dismiss() |
||||||
|
|
||||||
|
<CrashReportDetails@Popup> |
||||||
|
BoxLayout: |
||||||
|
orientation: 'vertical' |
||||||
|
ScrollView: |
||||||
|
do_scroll_x: False |
||||||
|
Label: |
||||||
|
id: contents |
||||||
|
text_size: root.width*.9, None |
||||||
|
size: self.texture_size |
||||||
|
size_hint: None, None |
||||||
|
Button: |
||||||
|
text: 'Close' |
||||||
|
height: '48dp' |
||||||
|
size_hint: 1, None |
||||||
|
on_release: root.dismiss() |
||||||
|
''') |
||||||
|
|
||||||
|
|
||||||
|
class CrashReporter(BaseCrashReporter, Factory.Popup): |
||||||
|
issue_template = """[b]Traceback[/b] |
||||||
|
|
||||||
|
[i]{traceback}[/i] |
||||||
|
|
||||||
|
|
||||||
|
[b]Additional information[/b] |
||||||
|
* Electrum version: {app_version} |
||||||
|
* Operating system: {os} |
||||||
|
* Wallet type: {wallet_type} |
||||||
|
* Locale: {locale} |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__(self, main_window, exctype, value, tb): |
||||||
|
BaseCrashReporter.__init__(self, exctype, value, tb) |
||||||
|
Factory.Popup.__init__(self) |
||||||
|
self.main_window = main_window |
||||||
|
self.title = BaseCrashReporter.CRASH_TITLE |
||||||
|
self.title_size = "24sp" |
||||||
|
self.ids.crash_message.text = BaseCrashReporter.CRASH_MESSAGE |
||||||
|
self.ids.request_help_message.text = BaseCrashReporter.REQUEST_HELP_MESSAGE |
||||||
|
self.ids.describe_error_message.text = BaseCrashReporter.DESCRIBE_ERROR_MESSAGE |
||||||
|
|
||||||
|
def show_contents(self): |
||||||
|
details = CrashReportDetails(self.get_report_string()) |
||||||
|
details.open() |
||||||
|
|
||||||
|
def show_popup(self, title, content): |
||||||
|
popup = Factory.Popup(title=title, |
||||||
|
content=Label(text=content, text_size=(Window.size[0] * 3/4, None)), |
||||||
|
size_hint=(3/4, 3/4)) |
||||||
|
popup.open() |
||||||
|
|
||||||
|
def send_report(self): |
||||||
|
try: |
||||||
|
response = BaseCrashReporter.send_report(self, "/crash.json").json() |
||||||
|
except requests.exceptions.RequestException: |
||||||
|
self.show_popup(_('Unable to send report'), _("Please check your network connection.")) |
||||||
|
else: |
||||||
|
self.show_popup(_('Report sent'), response["text"]) |
||||||
|
if response["location"]: |
||||||
|
self.open_url(response["location"]) |
||||||
|
self.dismiss() |
||||||
|
|
||||||
|
def open_url(self, url): |
||||||
|
if platform != 'android': |
||||||
|
return |
||||||
|
from jnius import autoclass, cast |
||||||
|
String = autoclass("java.lang.String") |
||||||
|
url = String(url) |
||||||
|
PythonActivity = autoclass('org.kivy.android.PythonActivity') |
||||||
|
activity = PythonActivity.mActivity |
||||||
|
Intent = autoclass('android.content.Intent') |
||||||
|
Uri = autoclass('android.net.Uri') |
||||||
|
browserIntent = Intent() |
||||||
|
# This line crashes the app: |
||||||
|
# browserIntent.setAction(Intent.ACTION_VIEW) |
||||||
|
# Luckily we don't need it because the OS is smart enough to recognize the URL |
||||||
|
browserIntent.setData(Uri.parse(url)) |
||||||
|
currentActivity = cast('android.app.Activity', activity) |
||||||
|
currentActivity.startActivity(browserIntent) |
||||||
|
|
||||||
|
def show_never(self): |
||||||
|
self.main_window.electrum_config.set_key(BaseCrashReporter.config_key, False) |
||||||
|
self.dismiss() |
||||||
|
|
||||||
|
def get_user_description(self): |
||||||
|
return self.ids.user_message.text |
||||||
|
|
||||||
|
def get_wallet_type(self): |
||||||
|
return self.main_window.wallet.wallet_type |
||||||
|
|
||||||
|
def get_os_version(self): |
||||||
|
if utils.platform is not "android": |
||||||
|
return utils.platform |
||||||
|
import jnius |
||||||
|
bv = jnius.autoclass('android.os.Build$VERSION') |
||||||
|
b = jnius.autoclass('android.os.Build') |
||||||
|
return "Android {} on {} {} ({})".format(bv.RELEASE, b.BRAND, b.DEVICE, b.DISPLAY) |
||||||
|
|
||||||
|
|
||||||
|
class CrashReportDetails(Factory.Popup): |
||||||
|
def __init__(self, text): |
||||||
|
Factory.Popup.__init__(self) |
||||||
|
self.title = "Report Details" |
||||||
|
self.ids.contents.text = text |
||||||
|
print(text) |
||||||
|
|
||||||
|
|
||||||
|
class ExceptionHook(base.ExceptionHandler): |
||||||
|
def __init__(self, main_window): |
||||||
|
super().__init__() |
||||||
|
self.main_window = main_window |
||||||
|
if not main_window.electrum_config.get(BaseCrashReporter.config_key, default=True): |
||||||
|
return |
||||||
|
# For exceptions in Kivy: |
||||||
|
base.ExceptionManager.add_handler(self) |
||||||
|
# For everything else: |
||||||
|
sys.excepthook = lambda exctype, value, tb: self.handle_exception(value) |
||||||
|
|
||||||
|
def handle_exception(self, _inst): |
||||||
|
exc_info = sys.exc_info() |
||||||
|
# Check if this is an exception from within the exception handler: |
||||||
|
import traceback |
||||||
|
for item in traceback.extract_tb(exc_info[2]): |
||||||
|
if item.filename.endswith("crash_reporter.py"): |
||||||
|
return |
||||||
|
e = CrashReporter(self.main_window, *exc_info) |
||||||
|
# Open in main thread: |
||||||
|
Clock.schedule_once(lambda _: e.open(), 0) |
||||||
|
return base.ExceptionManager.PASS |
||||||
@ -0,0 +1,125 @@ |
|||||||
|
# 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 traceback |
||||||
|
import subprocess |
||||||
|
import sys |
||||||
|
import os |
||||||
|
|
||||||
|
import requests |
||||||
|
|
||||||
|
from electrum import ELECTRUM_VERSION, constants |
||||||
|
from electrum.i18n import _ |
||||||
|
|
||||||
|
|
||||||
|
class BaseCrashReporter(object): |
||||||
|
report_server = "https://crashhub.electrum.org" |
||||||
|
config_key = "show_crash_reporter" |
||||||
|
issue_template = """<h2>Traceback</h2> |
||||||
|
<pre> |
||||||
|
{traceback} |
||||||
|
</pre> |
||||||
|
|
||||||
|
<h2>Additional information</h2> |
||||||
|
<ul> |
||||||
|
<li>Electrum version: {app_version}</li> |
||||||
|
<li>Operating system: {os}</li> |
||||||
|
<li>Wallet type: {wallet_type}</li> |
||||||
|
<li>Locale: {locale}</li> |
||||||
|
</ul> |
||||||
|
""" |
||||||
|
CRASH_MESSAGE = _('Something went wrong while executing Electrum.') |
||||||
|
CRASH_TITLE = _('Sorry!') |
||||||
|
REQUEST_HELP_MESSAGE = _('To help us diagnose and fix the problem, you can send us a bug report that contains ' |
||||||
|
'useful debug information:') |
||||||
|
DESCRIBE_ERROR_MESSAGE = _("Please briefly describe what led to the error (optional):") |
||||||
|
ASK_CONFIRM_SEND = _("Do you want to send this report?") |
||||||
|
|
||||||
|
def __init__(self, exctype, value, tb): |
||||||
|
self.exc_args = (exctype, value, tb) |
||||||
|
|
||||||
|
def send_report(self, endpoint="/crash"): |
||||||
|
if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server: |
||||||
|
# Gah! Some kind of altcoin wants to send us crash reports. |
||||||
|
raise BaseException(_("Missing report URL.")) |
||||||
|
report = self.get_traceback_info() |
||||||
|
report.update(self.get_additional_info()) |
||||||
|
report = json.dumps(report) |
||||||
|
response = requests.post(BaseCrashReporter.report_server + endpoint, data=report) |
||||||
|
return response |
||||||
|
|
||||||
|
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 = { |
||||||
|
"app_version": ELECTRUM_VERSION, |
||||||
|
"os": self.get_os_version(), |
||||||
|
"wallet_type": "unknown", |
||||||
|
"locale": locale.getdefaultlocale()[0] or "?", |
||||||
|
"description": self.get_user_description() |
||||||
|
} |
||||||
|
try: |
||||||
|
args["wallet_type"] = self.get_wallet_type() |
||||||
|
except: |
||||||
|
# Maybe the wallet isn't loaded yet |
||||||
|
pass |
||||||
|
try: |
||||||
|
args["app_version"] = self.get_git_version() |
||||||
|
except: |
||||||
|
# This is probably not running from source |
||||||
|
pass |
||||||
|
return args |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def get_git_version(): |
||||||
|
dir = os.path.dirname(os.path.realpath(sys.argv[0])) |
||||||
|
version = subprocess.check_output( |
||||||
|
['git', 'describe', '--always', '--dirty'], cwd=dir) |
||||||
|
return str(version, "utf8").strip() |
||||||
|
|
||||||
|
def get_report_string(self): |
||||||
|
info = self.get_additional_info() |
||||||
|
info["traceback"] = "".join(traceback.format_exception(*self.exc_args)) |
||||||
|
return self.issue_template.format(**info) |
||||||
|
|
||||||
|
def get_user_description(self): |
||||||
|
raise NotImplementedError |
||||||
|
|
||||||
|
def get_wallet_type(self): |
||||||
|
raise NotImplementedError |
||||||
|
|
||||||
|
def get_os_version(self): |
||||||
|
raise NotImplementedError |
||||||
Loading…
Reference in new issue