diff --git a/linux/keyman-config/keyman_config/__init__.py b/linux/keyman-config/keyman_config/__init__.py index 26fbeddf7d..88371a8be9 100644 --- a/linux/keyman-config/keyman_config/__init__.py +++ b/linux/keyman-config/keyman_config/__init__.py @@ -110,27 +110,30 @@ def _set_dbus_started_for_session(value): def verify_dbus_running(): - if not 'DBUS_SESSION_BUS_ADDRESS' in os.environ: - try: - # Seems dbus isn't running for the current user. Try to start it - # and set these environment variables - logging.info('Starting dbus with dbus-launch') - stdout = subprocess.run( - ('dbus-launch', '--exit-with-session'), - stdout=subprocess.PIPE, check=False).stdout - _set_dbus_started_for_session(True) - lines = stdout.decode('utf-8').splitlines() - for line in lines: - equal_sign = line.find('=') - if equal_sign <= 0: - logging.warning('Got unexpected line from dbus-launch: %s', line) - continue - name = line[:equal_sign] - value = line[equal_sign+1:] - logging.debug('Setting environment %s=%s', name, value) - os.environ[name] = value - except Exception as e: - logging.error('Starting dbus-launch failed with %s', e) + if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: + # already running - nothing to do + return + + try: + # Seems dbus isn't running for the current user. Try to start it + # and set these environment variables + logging.info('Starting dbus with dbus-launch') + stdout = subprocess.run( + ('dbus-launch', '--exit-with-session'), + stdout=subprocess.PIPE, check=False).stdout + _set_dbus_started_for_session(True) + lines = stdout.decode('utf-8').splitlines() + for line in lines: + equal_sign = line.find('=') + if equal_sign <= 0: + logging.warning('Got unexpected line from dbus-launch: %s', line) + continue + name = line[:equal_sign] + value = line[equal_sign+1:] + logging.debug('Setting environment %s=%s', name, value) + os.environ[name] = value + except Exception as e: + logging.error('Starting dbus-launch failed with %s', e) def add_standard_arguments(parser): diff --git a/linux/keyman-config/keyman_config/downloadkeyboard.py b/linux/keyman-config/keyman_config/downloadkeyboard.py index ca3b091f92..7eac44b5ec 100755 --- a/linux/keyman-config/keyman_config/downloadkeyboard.py +++ b/linux/keyman-config/keyman_config/downloadkeyboard.py @@ -94,8 +94,7 @@ class DownloadKmpWindow(Gtk.Dialog): qs = urllib.parse.parse_qs(parsed.query) package_id = parsed.path.split('/')[-1] downloadfile = os.path.join(get_download_folder(), package_id) - download_url = KeymanComUrl + '/go/package/download/' + package_id + \ - '?platform=linux&tier=' + __tier__ + download_url = f'{KeymanComUrl}/go/package/download/{package_id}?platform=linux&tier={__tier__}' if 'bcp47' in qs: self.language = qs['bcp47'][0] download_url += '&bcp47=' + qs['bcp47'][0] diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index 3addda759b..104036c7fb 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -13,6 +13,7 @@ from gi.repository import GObject from keyman_config import _ from keyman_config import KeymanApiUrl, KeymanDownloadsUrl from keyman_config.deprecated_decorator import deprecated +from keyman_config.sentry_handling import SentryErrorHandling class InstallLocation(GObject.GEnum): @@ -105,7 +106,7 @@ def get_keyboard_data(keyboardID, weekCache=False): dict: Keyboard data """ logging.info('Getting data for keyboard %s', keyboardID) - api_url = KeymanApiUrl + '/keyboard/' + keyboardID + api_url = f'{KeymanApiUrl}/keyboard/{keyboardID}' logging.debug('At URL %s', api_url) cache_dir = keyman_cache_dir() current_dir = os.getcwd() @@ -123,10 +124,7 @@ def get_keyboard_data(keyboardID, weekCache=False): logging.debug('Time: {0} / Used Cache: {1}'.format(now, _did_use_cache(response))) os.chdir(current_dir) _uninstall_cache() - if response.status_code == 200: - return response.json() - else: - return None + return response.json() if response.status_code == 200 else None def get_download_folder(): @@ -234,6 +232,8 @@ def download_kmp_file(url, kmpfile, cache=False): """ logging.info('Download URL: %s', url) downloadfile = None + sentry = SentryErrorHandling() + sentry.add_breadcrumb(category='download', message=f'Downloading keyboard from {url}') if cache: cache_dir = keyman_cache_dir() diff --git a/linux/keyman-config/keyman_config/gnome_keyboards_util.py b/linux/keyman-config/keyman_config/gnome_keyboards_util.py index 0072215a48..030f4d9234 100644 --- a/linux/keyman-config/keyman_config/gnome_keyboards_util.py +++ b/linux/keyman-config/keyman_config/gnome_keyboards_util.py @@ -3,6 +3,7 @@ import logging import os from keyman_config.gsettings import GSettings +from keyman_config.sentry_handling import SentryErrorHandling # pylint: disable=global-statement @@ -52,15 +53,20 @@ def _reset_gnome_shell(): def get_ibus_keyboard_id(keyboard, packageDir, language=None, ignore_language=False): if not keyboard: return None + sentry = SentryErrorHandling() kmx_file = os.path.join(packageDir, keyboard['id'] + ".kmx") if ignore_language: + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(1): {kmx_file}') return kmx_file if language is not None and language != '': logging.debug(language) + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(2): {language}:{kmx_file}') return f"{language}:{kmx_file}" if "languages" in keyboard and len(keyboard["languages"]) > 0: firstLanguage = keyboard["languages"][0] logging.debug(firstLanguage) if 'id' in firstLanguage: + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(3): {firstLanguage["id"]}:{kmx_file}') return f"{firstLanguage['id']}:{kmx_file}" + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(4): {kmx_file}') return kmx_file diff --git a/linux/keyman-config/keyman_config/handle_install.py b/linux/keyman-config/keyman_config/handle_install.py index f551a63dd6..9933978a57 100644 --- a/linux/keyman-config/keyman_config/handle_install.py +++ b/linux/keyman-config/keyman_config/handle_install.py @@ -6,6 +6,7 @@ from zipfile import is_zipfile from keyman_config import KeymanComUrl, __tier__ from keyman_config.get_kmp import download_kmp_file, get_download_folder from keyman_config.install_window import InstallKmpWindow +from keyman_config.sentry_handling import SentryErrorHandling def download_and_install_package(url): @@ -18,6 +19,7 @@ def download_and_install_package(url): url: a .kmp file, a keyman:// URL, or a file:// URL pointing to a .kmp file, possibly with a bcp47= specified """ + sentry = SentryErrorHandling() parsedUrl = urlparse(url) bcp47 = _extract_bcp47(parsedUrl.query) @@ -34,11 +36,13 @@ def download_and_install_package(url): downloadFile = os.path.join(get_download_folder(), packageId) downloadUrl = f'{KeymanComUrl}/go/package/download/{packageId}?platform=linux&tier={__tier__}' + sentry.add_breadcrumb(category='download', message=f'About to download keyboard {packageId} from {downloadUrl}') packageFile = download_kmp_file(downloadUrl, downloadFile) if packageFile is None: return elif parsedUrl.scheme in ['', 'file']: packageFile = parsedUrl.path + sentry.add_breadcrumb(category='keyboard', message=f'adding keyboard from file {packageFile}') else: logging.error(f"Invalid URL: {url}") return diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index 5320a00d3a..4848416a5b 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -23,6 +23,7 @@ from keyman_config.gnome_keyboards_util import (GnomeKeyboardsUtil, from keyman_config.ibus_util import get_ibus_bus, install_to_ibus, restart_ibus from keyman_config.kmpmetadata import KMFileTypes, get_metadata from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml +from keyman_config.sentry_handling import SentryErrorHandling # TODO userdir install # special processing for kmn if needed @@ -105,6 +106,9 @@ class InstallKmp(): self.kmpdocdir = get_keyman_doc_dir(area, self.packageID) self.kmpfontdir = get_keyman_font_dir(area, self.packageID) + sentry = SentryErrorHandling() + sentry.add_breadcrumb(category='install', message=f'Installing kmp: {self.packageID} from "{inputfile}" for lang: {language}; area: {"OS" if area == 1 else "Shared" if area == 2 else "User" if area == 3 else "Unknown"}') + if not os.path.isfile(inputfile): message = _("File {kmpfile} doesn't exist").format(kmpfile=inputfile) logging.error("install_kmp.py: %s", message) @@ -258,6 +262,9 @@ class InstallKmp(): if not language: language = self._add_custom_keyboard(firstKeyboard, packageDir, requested_language) + sentry = SentryErrorHandling() + sentry.add_breadcrumb(category='install', message=f'Installing keyboards for lang: {language}') + if is_fcitx_running(): return self._install_keyboards_to_fcitx() diff --git a/linux/keyman-config/keyman_config/kmpmetadata.py b/linux/keyman-config/keyman_config/kmpmetadata.py index 82e90b138d..42fa9774a6 100755 --- a/linux/keyman-config/keyman_config/kmpmetadata.py +++ b/linux/keyman-config/keyman_config/kmpmetadata.py @@ -11,6 +11,7 @@ from json.decoder import JSONDecodeError import magic from keyman_config import secure_lookup +from keyman_config.sentry_handling import SentryErrorHandling class KMFileTypes(Enum): @@ -455,6 +456,7 @@ def parsemetadata(jsonfile, verbose=False): """ info = system = keyboards = files = options = nonexistent = None extracted_dir = os.path.dirname(jsonfile) + sentry = SentryErrorHandling() logging.debug("parsemetadata: reading file:%s dir:%s", jsonfile, extracted_dir) @@ -496,6 +498,11 @@ def parsemetadata(jsonfile, verbose=False): print_options(options) print_keyboards(keyboards) print_files(files, extracted_dir) + + if system: + sentry.add_breadcrumb( + category='keyboard', message=f'kmp.json: pkg: "{secure_lookup(info, "name", "description")}", KeymanDeveloperVersion: {secure_lookup(system, "keymanDeveloperVersion")}, dir: {os.path.basename(extracted_dir)}') + return info, system, options, keyboards, files @@ -532,19 +539,22 @@ def get_and_convert_infdata(tmpdirname): see kmpmetadata.parseinfdata for details """ kmpinf = os.path.join(tmpdirname, "kmp.inf") - if os.path.isfile(kmpinf): - info, system, options, keyboards, files = parseinfdata(kmpinf, False) - j = infmetadata_to_json(info, system, options, keyboards, files) - kmpjson = os.path.join(tmpdirname, "kmp.json") - try: - with open(kmpjson, "w") as write_file: - print(j, file=write_file) - except Exception as e: - logging.warning('Exception %s writing metadata %s %s', type(e), kmpjson, e.args) - return info, system, options, keyboards, files - else: + if not os.path.isfile(kmpinf): return None, None, None, None, None + sentry = SentryErrorHandling() + info, system, options, keyboards, files = parseinfdata(kmpinf, False) + j = infmetadata_to_json(info, system, options, keyboards, files) + kmpjson = os.path.join(tmpdirname, "kmp.json") + try: + with open(kmpjson, "w") as write_file: + print(j, file=write_file) + except Exception as e: + logging.warning('Exception %s writing metadata %s %s', type(e), kmpjson, e.args) + sentry.add_breadcrumb( + category='keyboard', message=f'kmp.inf: pkg: "{secure_lookup(info, "name", "description")}", KeymanDeveloperVersion: {secure_lookup(system, "keymanDeveloperVersion")}, dir: {os.path.basename(tmpdirname)}') + return info, system, options, keyboards, files + def infmetadata_to_json(info, system, options, keyboards, files): jsonfiles = [] diff --git a/linux/keyman-config/keyman_config/sentry_handling.py b/linux/keyman-config/keyman_config/sentry_handling.py index e7b3d40da3..b153066a1a 100644 --- a/linux/keyman-config/keyman_config/sentry_handling.py +++ b/linux/keyman-config/keyman_config/sentry_handling.py @@ -30,6 +30,9 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gio, Gtk +is_sentry_sdk = False + + class SentryErrorHandling: def __init__(self) -> None: self.error_reporting_setting = KeymanOption('error-reporting') @@ -78,6 +81,15 @@ class SentryErrorHandling: if enabled != was_enabled: self._handle_enabled(enabled) + def add_breadcrumb(self, category, message, level='info'): + global is_sentry_sdk + if not self.is_sentry_enabled() or not is_sentry_sdk: + return + + # sentry_sdk = importlib.import_module('sentry_sdk') + import sentry_sdk + sentry_sdk.add_breadcrumb(category=category, message=message, level=level) + def _get_environ_nosentry(self): keyman_nosentry = os.environ.get('KEYMAN_NOSENTRY') return keyman_nosentry and (int(keyman_nosentry) == 1) @@ -114,47 +126,61 @@ class SentryErrorHandling: def _sentry_sdk_initialize(self): # Try new sentry-sdk first + global is_sentry_sdk sentry_sdk = importlib.import_module('sentry_sdk') from sentry_sdk import configure_scope, set_user from sentry_sdk.integrations.logging import LoggingIntegration + is_sentry_sdk = True + sentry_logging = LoggingIntegration( level=logging.INFO, # Capture info and above as breadcrumbs event_level=logging.CRITICAL # Send critical errors as events ) SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@o1005580.ingest.sentry.io/5983525" + + os_release = None + dist = None + try: + os_release = platform.freedesktop_os_release() + dist = os_release['VERSION_CODENAME'] + except OSError as e: + logging.debug(f'System does not have os_release file: {e.strerror}') + except AttributeError: + logging.debug('System does not have platform.freedesktop_os_release() method') + except Exception: + logging.debug( + 'Got exception trying to access platform.freedesktop_os_release() method or os_release information') + sentry_sdk.init( dsn=SentryUrl, environment=__environment__, release=__versiongittag__, integrations=[sentry_logging], + dist=dist, + in_app_include=['keyman_config'], before_send=self._before_send ) - hash = hashlib.md5() - hash.update(getpass.getuser().encode()) - set_user({'id': hash.hexdigest()}) + md5Hash = hashlib.md5() + md5Hash.update(getpass.getuser().encode()) + set_user({'id': md5Hash.hexdigest()}) with configure_scope() as scope: - scope.set_tag("app", os.path.basename(sys.argv[0])) - scope.set_tag("pkgversion", __pkgversion__) - scope.set_tag("platform", platform.platform()) - scope.set_tag("system", platform.system()) - scope.set_tag("tier", __tier__) - scope.set_tag("device", platform.node()) - try: - os_release = platform.freedesktop_os_release() - scope.set_tag('os', os_release['PRETTY_NAME']) - scope.set_tag('os.name', os_release['NAME']) - if 'VERSION' in os_release: - scope.set_tag('os.version', os_release['VERSION']) - except OSError as e: - logging.debug(f'System does not have os_release file: {e.strerror}') - except AttributeError: - logging.debug('System does not have platform.freedesktop_os_release() method') - except: - logging.debug( - 'Got exception trying to access platform.freedesktop_os_release() method or os_release information') + self._set_system_info(scope, os_release) logging.info("Initialized Sentry error reporting") + def _set_system_info(self, scope, os_release): + scope.set_tag("app", os.path.basename(sys.argv[0])) + scope.set_tag("pkgversion", __pkgversion__) + scope.set_tag("platform", platform.platform()) + scope.set_tag("system", platform.system()) + scope.set_tag("tier", __tier__) + scope.set_tag("device", platform.node()) + if os_release: + scope.set_tag('os', os_release['PRETTY_NAME']) + scope.set_tag('os.name', os_release['NAME']) + if 'VERSION' in os_release: + scope.set_tag('os.version', os_release['VERSION']) + def _raven_initialize(self): # sentry-sdk is not available, so use older raven raven = importlib.import_module('raven') diff --git a/linux/keyman-config/km-config b/linux/keyman-config/km-config index 21072329a8..bfb2063c90 100755 --- a/linux/keyman-config/km-config +++ b/linux/keyman-config/km-config @@ -71,7 +71,7 @@ if __name__ == '__main__': download_and_install_package(args.url) else: # Workaround for bug in webkit2gtk (#12587) - if not 'WEBKIT_DISABLE_DMABUF_RENDERER' in os.environ: + if 'WEBKIT_DISABLE_DMABUF_RENDERER' not in os.environ: os.environ['WEBKIT_DISABLE_DMABUF_RENDERER'] = '1' w = ViewInstalledWindow()