Commit c794ddcf authored by Daniel Brötzmann's avatar Daniel Brötzmann
Browse files

Exceptions: Rework and basic Sentry integration

parent c2feb0ef
Pipeline #9012 passed with stages
in 5 minutes and 46 seconds
......@@ -139,6 +139,7 @@ def __init__(self):
'IDLE': False,
'APPINDICATOR': False,
'AYATANA_APPINDICATOR': False,
'SENTRY_SDK': False,
}
_tasks: dict[int, list[Any]] = defaultdict(list)
......@@ -304,6 +305,13 @@ def detect_dependencies() -> None:
except (ImportError, ValueError):
pass
# SENTRY SDK
try:
import sentry_sdk # pylint: disable=unused-import
_dependencies['SENTRY_SDK'] = True
except ImportError:
pass
# Print results
for dep, val in _dependencies.items():
log('gajim').info('%-13s %s', dep, val)
......
......@@ -78,6 +78,7 @@
from OpenSSL.crypto import FILETYPE_PEM
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
import precis_i18n.codec # pylint: disable=unused-import
from gajim.common import app
......@@ -1426,3 +1427,39 @@ def get_start_of_day(date_time: datetime) -> datetime:
minute=0,
second=0,
microsecond=0)
def get_os_name() -> str:
if sys.platform in ('win32', 'darwin'):
return platform.system()
if os.name == 'posix':
try:
import distro
return distro.name(pretty=True)
except ImportError:
return platform.system()
return ''
def get_os_version() -> str:
if sys.platform in ('win32', 'darwin'):
return platform.release()
if os.name == 'posix':
try:
import distro
return distro.version(pretty=True)
except ImportError:
return platform.release()
return ''
def get_gobject_version() -> str:
gobject_ver = '.'.join(map(str, GObject.pygobject_version))
return gobject_ver
def get_glib_version() -> str:
glib_ver = '.'.join(map(str, [GLib.MAJOR_VERSION,
GLib.MINOR_VERSION,
GLib.MICRO_VERSION]))
return glib_ver
......@@ -149,6 +149,7 @@ class _ACCOUNT_DEFAULT:
'video_framerate',
'video_input_device',
'video_size',
'sentry_endpoint',
]
AllSettings = Literal[BoolSettings, IntSettings, StringSettings]
......@@ -268,6 +269,7 @@ class _ACCOUNT_DEFAULT:
'gc_print_join_left_default': False,
'check_for_update': True,
'last_update_check': '',
'sentry_endpoint': 'http://a82a9566841e4844b711d1813680496f@194.59.207.70:9000/3',
'always_ask_for_status_message': False,
'show_send_message_button': False,
'workspace_order': [],
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkApplicationWindow" id="exception_dialog">
<property name="can_focus">False</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<requires lib="gtk+" version="3.24"/>
<object class="GtkBox" id="exception_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkInfoBar" id="infobar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="revealed">False</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout-style">end</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Install &lt;tt&gt;sentry-sdk&lt;/tt&gt; to make reporting issues easier.</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">dialog-information-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="can-focus">False</property>
<property name="border-width">18</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<property name="can-focus">False</property>
<property name="spacing">24</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="icon_name">dialog-error</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="icon-name">dialog-error</property>
<property name="icon_size">6</property>
</object>
<packing>
......@@ -35,17 +95,17 @@
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Sorry, that should not have happened</property>
<property name="wrap">True</property>
<property name="max_width_chars">30</property>
<property name="max-width-chars">50</property>
<property name="xalign">0</property>
<style>
<class name="large-header"/>
......@@ -60,14 +120,17 @@
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Gajim encountered an error. A report is shown below.
By reporting this bug you might help people to fix this.</property>
<property name="label" translatable="yes">Gajim encountered an error.
Please help us fixing this issue by sending this report.</property>
<property name="wrap">True</property>
<property name="max_width_chars">60</property>
<property name="max-width-chars">60</property>
<property name="lines">3</property>
<property name="xalign">0</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="expand">False</property>
......@@ -75,9 +138,95 @@ By reporting this bug you might help people to fix this.</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkExpander">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="label-fill">True</property>
<property name="resize-toplevel">True</property>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTextView" id="exception_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="editable">False</property>
<property name="left-margin">4</property>
<property name="right-margin">4</property>
<property name="top-margin">4</property>
<property name="bottom-margin">4</property>
<property name="accepts-tab">False</property>
<property name="monospace">True</property>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">View Report</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox" id="user_feedback_box">
<property name="can-focus">False</property>
<property name="margin-top">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Additional Infos:</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="user_feedback_entry">
<property name="width-request">400</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="activates-default">True</property>
<property name="placeholder-text" translatable="yes">Add some details…</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
......@@ -90,79 +239,72 @@ By reporting this bug you might help people to fix this.</property>
</packing>
</child>
<child>
<object class="GtkButtonBox">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkButton" id="close_btn">
<property name="label">_Close</property>
<object class="GtkButton" id="close_button">
<property name="label" translatable="yes">_Close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_close_clicked" swapped="no"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="_on_close_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="report_btn">
<property name="label" translatable="yes">_Report Bug</property>
<object class="GtkButton" id="report_button">
<property name="label" translatable="yes">Send _Report</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_report_clicked" swapped="no"/>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="_on_report_clicked" swapped="no"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="exception_view">
<object class="GtkSpinner" id="report_spinner">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="editable">False</property>
<property name="left_margin">4</property>
<property name="right_margin">4</property>
<property name="accepts_tab">False</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>
......@@ -17,29 +17,46 @@
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from typing import Type
from types import TracebackType
import sys
import os
import json
from json.decoder import JSONDecodeError
import traceback
import threading
import webbrowser
import platform
from io import StringIO
from urllib.parse import urlencode
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Soup
import nbxmpp
import gajim
from gajim.common import app
from gajim.common import configpaths
from gajim.common.helpers import get_glib_version
from gajim.common.helpers import get_gobject_version
from gajim.common.helpers import get_os_name
from gajim.common.helpers import get_os_version
from gajim.common.i18n import _
from .builder import get_builder
from .util import get_gtk_version
try:
import sentry_sdk
except ImportError:
pass
_exception_in_progress = threading.Lock()
ISSUE_TEXT = '''## Versions
ISSUE_URL = 'https://dev.gajim.org/gajim/gajim/issues/new'
ISSUE_TEXT = '''## Versions:
- OS: {}
- GTK Version: {}
- PyGObject Version: {}
......@@ -55,7 +72,10 @@
...'''
def _hook(type_, value, tb):
def _hook(type_: Type[BaseException],
value: BaseException,
tb: TracebackType
) -> None:
if not _exception_in_progress.acquire(False):
# Exceptions have piled up, so we use the default exception
# handler for such exceptions
......@@ -66,65 +86,135 @@ def _hook(type_, value, tb):
_exception_in_progress.release()
class ExceptionDialog():
def __init__(self, type_, value, tb):
class ExceptionDialog(Gtk.ApplicationWindow):
def __init__(self,
type_: Type[BaseException],
value: BaseException,
tb: TracebackType
) -> None:
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_resizable(True)
self.set_default_size(700, -1)
self.set_title(_('Gajim - Error'))
self._traceback_data = (type_, value, tb)
self._sentry_available = app.is_installed('SENTRY_SDK')
self._sentry_endpoint = app.settings.get('sentry_endpoint')
path = configpaths.get('GUI') / 'exception_dialog.ui'
self._ui = get_builder(path.resolve())
self._ui.connect_signals(self)
self.add(self._ui.exception_box)
self._ui.report_btn.grab_focus()
if not self._sentry_available:
self._ui.user_feedback_box.set_no_show_all(True)
self._ui.infobar.set_revealed(True)
self._ui.report_button.grab_focus()
self._ui.report_button.grab_default()
buffer_ = self._ui.exception_view.get_buffer()
trace = StringIO()
traceback.print_exception(type_, value, tb, None, trace)
self.text = self.get_issue_text(trace.getvalue())
buffer_.set_text(self.text)
print(self.text, file=sys.stderr)
self._ui.exception_view.set_editable(False)
self._ui.exception_dialog.show()
self._issue_text = self._get_issue_text(trace.getvalue())
buffer_ = self._ui.exception_view.get_buffer()
buffer_.set_text(self._issue_text)
self._ui.connect_signals(self)
self.show_all()
if self._sentry_available:
self._ui.user_feedback_entry.grab_focus()
def on_report_clicked(self, *args):
issue_url = 'https://dev.gajim.org/gajim/gajim/issues/new'
params = {'issue[description]': self.text}
url = '{}?{}'.format(issue_url, urlencode(params))
def _on_report_clicked(self, _button: Gtk.Button) -> None:
if self._sentry_available:
self._report_via_sentry()
return
params = {'issue[description]': self._issue_text}
url = f'{ISSUE_URL}?{urlencode(params)}'
webbrowser.open(url, new=2)
self.destroy()
def on_close_clicked(self, *args):
self._ui.exception_dialog.destroy()
def _on_close_clicked(self, _button: Gtk.Button) -> None:
self.destroy()
@staticmethod
def get_issue_text(traceback_text):
gtk_ver = '%i.%i.%i' % (
Gtk.get_major_version(),
Gtk.get_minor_version(),
Gtk.get_micro_version())
gobject_ver = '.'.join(map(str, GObject.pygobject_version))
glib_ver = '.'.join(map(str, [GLib.MAJOR_VERSION,
GLib.MINOR_VERSION,
GLib.MICRO_VERSION]))
return ISSUE_TEXT.format(get_os_info(),
gtk_ver,
gobject_ver,
glib_ver,
nbxmpp.__version__,
gajim.__version__,
traceback_text)
def init():
if os.name == 'nt' or not sys.stderr.isatty():
def _get_issue_text(traceback_text: str) -> str:
return ISSUE_TEXT.format(
f'{get_os_name()} {get_os_version()}',
get_gtk_version(),
get_gobject_version(),