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 server3.2.x
committed by
ghost43
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