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