Merge branch 'stable-18.0' into maint/cherrypick/builder-autocomplete-inc-sh

This commit is contained in:
Joshua Horton 2025-05-21 13:16:45 +07:00
commit 5909086bf7
7 changed files with 143 additions and 9 deletions

View file

@ -4,15 +4,19 @@
package com.keyman.engine.util;
import static com.keyman.engine.KMManager.KMKey_LexicalModelID;
import android.util.Log;
import android.widget.Toast;
import com.keyman.engine.BaseActivity;
import com.keyman.engine.BuildConfig;
import com.keyman.engine.KMManager;
import com.keyman.engine.util.DependencyUtil;
import com.keyman.engine.data.Keyboard;
import com.keyman.engine.util.DependencyUtil.LibraryType;
import java.util.Map;
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
@ -20,19 +24,67 @@ import io.sentry.SentryLevel;
public final class KMLog {
private static final String TAG = "KMLog";
private static final String KEYBOARD_TAG = "keyboardId";
private static final String KEYBOARD_COUNT_TAG = "installedKeyboardCount";
private static final String MODEL_TAG = "modelId";
private static final String LANGCODE_TAG = "languageCode";
// Some of the methods used to generate debug logging information can, themselves,
// trigger errors that can also trigger the same logging. We must not get
// caught in an infinite loop / stack-overflow; this field helps us avoid states
// that would otherwise cause error-looping, etc.
private static boolean isLogging = false;
private static void tagDebugInfo() {
String kbdId = "";
String lngCode = "";
String modelId = "";
int kbdCount = 0;
// Do not risk raising a new error while tagging info for another error.
try {
// Both take a context parameter... but don't actually need or use it!
Keyboard kbd = KMManager.getCurrentKeyboardInfo(null);
kbdCount = KMManager.getKeyboardsList(null).size();
if (kbd != null) {
kbdId = kbd.getKeyboardID();
lngCode = kbd.getLanguageCode();
Map<String, String> modelMap = KMManager.getAssociatedLexicalModel(kbd.getLanguageID());
if (modelMap != null) {
modelId = modelMap.get(KMKey_LexicalModelID);
if (modelId == null) {
modelId = "";
}
}
}
} catch (Exception ex) {
String msg = ex.getMessage() == null ? "" : ex.getMessage();
Sentry.setExtra("debugLoggingError", msg);
}
Sentry.setExtra(KEYBOARD_TAG, kbdId);
Sentry.setExtra(KEYBOARD_COUNT_TAG, "" + kbdCount);
Sentry.setExtra(LANGCODE_TAG, lngCode);
Sentry.setExtra(MODEL_TAG, modelId);
}
/**
* Utility to log info and send to Sentry
* @param tag String of the caller
* @param msg String of the info message
*/
public static void LogInfo(String tag, String msg) {
if(isLogging) {
return;
}
isLogging = true;
if (msg != null && !msg.isEmpty()) {
Log.i(tag, msg);
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
Sentry.captureMessage(msg, SentryLevel.INFO);
}
}
isLogging = false;
}
/**
@ -46,9 +98,15 @@ public final class KMLog {
return;
}
if(isLogging) {
return;
}
isLogging = true;
Log.i(tag, msg);
if (!DependencyUtil.libraryExists(LibraryType.SENTRY) || !Sentry.isEnabled()) {
isLogging = false;
return;
}
@ -74,7 +132,9 @@ public final class KMLog {
crumb.setData("stacktrace", trace);
}
}
tagDebugInfo();
Sentry.addBreadcrumb(crumb);
isLogging = false;
}
/**
@ -83,6 +143,10 @@ public final class KMLog {
* @param msg String of the error message
*/
public static void LogError(String tag, String msg) {
if(isLogging) {
return;
}
isLogging = true;
if (msg != null && !msg.isEmpty()) {
Log.e(tag, msg);
@ -91,9 +155,11 @@ public final class KMLog {
}
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
Sentry.captureMessage(msg, SentryLevel.ERROR);
}
}
isLogging = false;
}
/**
@ -103,6 +169,10 @@ public final class KMLog {
* @param e Throwable exception
*/
public static void LogException(String tag, String msg, Throwable e) {
if(isLogging) {
return;
}
isLogging = true;
String errorMsg = "";
if (msg != null && !msg.isEmpty()) {
errorMsg = msg + "\n" + e;
@ -116,9 +186,11 @@ public final class KMLog {
}
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
Sentry.addBreadcrumb(errorMsg);
Sentry.captureException(e);
}
isLogging = false;
}
/**
@ -131,7 +203,12 @@ public final class KMLog {
*/
public static void LogExceptionWithData(String tag, String msg,
String objName, Object obj, Throwable e) {
if(isLogging) {
return;
}
isLogging = true;
if (obj != null && DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
String objStr = null;
try {
objStr = obj.toString();
@ -141,6 +218,10 @@ public final class KMLog {
}
// Report the original exception
LogException(tag, msg, e);
// And remove the exception-specific tagged data, lest it also be
// tracked on subsequent errors not associated with the current call.
Sentry.removeExtra(objName);
}
isLogging = false;
}
}

View file

@ -16,10 +16,20 @@ import os.log
*/
public class SentryManager {
private static var _started: Bool = false
// Set once Manager.shared is fully initialized and safe to reference.
private static var engineHasInitialized: Bool = false
public static var hasStarted: Bool {
return _started
}
/**
This method should be called once Manager.shared is fully initialized and safe to reference - a precondition needed to enable engine-state logging, such as current keyboard, etc when errors occur.
*/
public static func setEngineInitialized() {
engineHasInitialized = true
}
public static func start(sendingEnabled: Bool = true) {
// First things first: enable Sentry for crash reporting.
@ -89,6 +99,31 @@ public class SentryManager {
return SentryManager.enabled
#endif
}
private static func setDebugInfo() {
// We cannot directly use Manager.shared while it's initializing,
// and there are logging lines during that process that can lead here!
guard engineHasInitialized else {
return
}
let kbd = Manager.shared.currentKeyboard
let modelId = Manager.shared.currentLexicalModelID?.id ?? ""
let kbdId = kbd?.id ?? ""
let langCode = kbd?.lgCode ?? ""
let kbdCnt = "\(Storage.active.userDefaults.userKeyboards?.count ?? 0)"
let engineState: [String: String] = [
"installedKeyboardCount": kbdCnt,
"keyboardId" : kbdId,
"languageCode" : langCode,
"modelId" : modelId,
]
SentrySDK.configureScope { scope in
scope.setContext(value: engineState, key: "engineState")
}
}
/**
* Captures a Sentry event and safely bypass the Sentry component if not activated by the app.
@ -96,6 +131,7 @@ public class SentryManager {
public static func capture(_ event: Sentry.Event) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
setDebugInfo()
SentrySDK.capture(event: event)
}
}
@ -131,6 +167,7 @@ public class SentryManager {
public static func breadcrumb(crumb: Sentry.Breadcrumb) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
setDebugInfo()
SentrySDK.addBreadcrumb(crumb)
}
}

View file

@ -249,8 +249,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), //
name: UIResponder.keyboardWillHideNotification, object: nil)
// We used to preload the old KeymanWebViewController, but now that it's embedded within the
// InputViewController, that's not exactly viable.
// Note that the engine has initialized and that Manager.shared
// is accessible for advanced logging info once this method ends.
SentryManager.setEngineInitialized()
}
// MARK: - Keyboard Notifications

View file

@ -3,5 +3,5 @@
[D-BUS Service]
Name=com.keyman.SystemService1
Exec=/usr/libexec/systemd-keyman.service
Exec=@LIBEXECDIR@/systemd-keyman.service
User=root

View file

@ -1,11 +1,26 @@
# This file will be appended to meson.build by build.sh
cfg = configuration_data()
cfg.set('LIBEXECDIR', get_option('prefix') / get_option('libexecdir'))
install_data('com.keyman.SystemService1.conf', install_dir: get_option('datadir') / 'dbus-1/system.d/')
if systemd.name() == 'libsystemd'
install_data('com.keyman.SystemService1.service.systemd', install_dir: get_option('datadir') / 'dbus-1/system-services/', rename: ['com.keyman.SystemService1.service'])
else
# libelogind or basu
install_data('com.keyman.SystemService1.service.basu', install_dir: get_option('datadir') / 'dbus-1/system-services/')
configure_file(
configuration: cfg,
input: 'com.keyman.SystemService1.service.basu',
output: 'com.keyman.SystemService1.service',
install: true,
install_dir: get_option('datadir') / 'dbus-1/system-services/'
)
endif
install_data('systemd-keyman.service', install_dir: get_option('prefix') / 'lib/systemd/system/')
configure_file(
configuration: cfg,
input: 'systemd-keyman.service.in',
output: 'systemd-keyman.service',
install: true,
install_dir: get_option('prefix') / 'lib/systemd/system/'
)

View file

@ -8,7 +8,7 @@ Description=Keyman System Service
[Service]
Type=dbus
BusName=com.keyman.SystemService1
ExecStart=/usr/libexec/keyman-system-service
ExecStart=@LIBEXECDIR@/keyman-system-service
Restart=on-failure
# Filesystem lockdown

View file

@ -7,9 +7,9 @@
<key>teamID</key>
<string>D7TR486TEH</string>
<key>signingCertificate</key>
<string>227D454DE9320031D7E9C3E02FEB7463B9A33776</string>
<string>D7B7973A30C90C1A04666667F24B4D5C7E913923</string>
<key>installerSigningCertificate</key>
<string>227D454DE9320031D7E9C3E02FEB7463B9A33776</string>
<string>D7B7973A30C90C1A04666667F24B4D5C7E913923</string>
<key>provisioningProfiles</key>
<dict>
<key>com.firstvoices.keyboards</key>