Merge pull request #10446 from keymanapp/chore/ios/8737-remove-xcglogger

chore(ios): eliminate xcglogger dependency
This commit is contained in:
Shawn Schantz 2024-01-29 12:31:37 +07:00 • committed by GitHub
commit 2ede49035a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 736 additions and 497 deletions

View file

@ -1,5 +1,4 @@
github "weichsel/ZIPFoundation" ~> 0.9
github "keymanapp/dependency-XCGLogger" "master"
github "devicekit/DeviceKit" ~> 5.0
github "ashleymills/Reachability.swift"
github "getsentry/sentry-cocoa" ~> 8.7.0

View file

@ -1,5 +1,4 @@
github "ashleymills/Reachability.swift" "v5.1.0"
github "devicekit/DeviceKit" "5.1.0"
github "getsentry/sentry-cocoa" "8.15.2"
github "keymanapp/dependency-XCGLogger" "57a7b975dbb6fe4fe90cef3d1bc52b8adbd89113"
github "weichsel/ZIPFoundation" "0.9.17"

View file

@ -26,6 +26,7 @@
1645D5972036C9F80076C51B /* KMPKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5962036C9F80076C51B /* KMPKeyboard.swift */; };
165EB3A12098993900040A69 /* KeyboardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB3A02098993900040A69 /* KeyboardError.swift */; };
296EF2C72AFA26C700E3E384 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */; };
29B30C232B564F9900C342A4 /* KeymanEngineLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B30C222B564F9900C342A4 /* KeymanEngineLogger.swift */; };
377D10DE26846B8900467431 /* SpacebarTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */; };
6CD5DFAA150F6DC8007A5DDE /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA8150F6DC8007A5DDE /* icon.png */; };
6CD5DFAB150F6DC8007A5DDE /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA9150F6DC8007A5DDE /* icon@2x.png */; };
@ -70,7 +71,6 @@
C0452BAD1F9F21270064431A /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0452BAC1F9F21270064431A /* Keyboard.swift */; };
C0452BAF1F9F22A80064431A /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0452BAE1F9F22A80064431A /* Font.swift */; };
C055E6EB1F99ED090035C2DD /* RegisteredFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = C055E6EA1F99ED090035C2DD /* RegisteredFont.swift */; };
C05B14331FD914870082A316 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05B14321FD914870082A316 /* Log.swift */; };
C05F43311FBD62550058CBD4 /* JSONDecoder.DateDecodingStrategy+ISO8601Fallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = C05F43301FBD62550058CBD4 /* JSONDecoder.DateDecodingStrategy+ISO8601Fallback.swift */; };
C06085B41F9485E40057E5B9 /* UIButton+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C06085B31F9485E40057E5B9 /* UIButton+Helpers.swift */; };
C06D37341F81F5C300F61AE0 /* HTTPDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0ED71B41F6BBFAF002A2FD6 /* HTTPDownloader.swift */; };
@ -121,10 +121,8 @@
CE46D65D247B93C9005FD506 /* (null) in Resources */ = {isa = PBXBuildFile; };
CE5C8BE324B5B3BA00FAFB7F /* Queries+LexicalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5C8BE224B5B3BA00FAFB7F /* Queries+LexicalModel.swift */; };
CE5C8BE524B5BD1C00FAFB7F /* QueryModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5C8BE424B5BD1C00FAFB7F /* QueryModelTests.swift */; };
CE5EDDC526522EAA001733AC /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC026522EA9001733AC /* XCGLogger.xcframework */; };
CE5EDDC726522EAA001733AC /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC226522EAA001733AC /* Sentry.xcframework */; };
CE5EDDC826522EAA001733AC /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC326522EAA001733AC /* Reachability.xcframework */; };
CE5EDDC926522EAA001733AC /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC426522EAA001733AC /* ObjcExceptionBridging.xcframework */; };
CE5EDDD52652372A001733AC /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDBB26522EA3001733AC /* DeviceKit.xcframework */; };
CE67D961228A6F190029F2B5 /* KeyboardCommandStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE67D960228A6F190029F2B5 /* KeyboardCommandStructs.swift */; };
CE71705823A9C14D00A924A1 /* ResourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE71705723A9C14D00A924A1 /* ResourceFileManager.swift */; };
@ -327,6 +325,7 @@
29B27FEF29062CF50036917E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
29B27FF029062D100036917E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
29B27FF129062D190036917E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
29B30C222B564F9900C342A4 /* KeymanEngineLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanEngineLogger.swift; sourceTree = "<group>"; };
29BFA75E28293287009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
29BFA75F282934B4009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
29BFA760282934BB009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -391,7 +390,6 @@
C0452BAC1F9F21270064431A /* Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = "<group>"; };
C0452BAE1F9F22A80064431A /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
C055E6EA1F99ED090035C2DD /* RegisteredFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredFont.swift; sourceTree = "<group>"; };
C05B14321FD914870082A316 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
C05F43301FBD62550058CBD4 /* JSONDecoder.DateDecodingStrategy+ISO8601Fallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONDecoder.DateDecodingStrategy+ISO8601Fallback.swift"; sourceTree = "<group>"; };
C06085B31F9485E40057E5B9 /* UIButton+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Helpers.swift"; sourceTree = "<group>"; };
C06D372B1F81F4E100F61AE0 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -436,10 +434,8 @@
CE5C8BE224B5B3BA00FAFB7F /* Queries+LexicalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Queries+LexicalModel.swift"; sourceTree = "<group>"; };
CE5C8BE424B5BD1C00FAFB7F /* QueryModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryModelTests.swift; sourceTree = "<group>"; };
CE5EDDBB26522EA3001733AC /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = "<group>"; };
CE5EDDC026522EA9001733AC /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = "<group>"; };
CE5EDDC226522EAA001733AC /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = "<group>"; };
CE5EDDC326522EAA001733AC /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = "<group>"; };
CE5EDDC426522EAA001733AC /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = "<group>"; };
CE67D960228A6F190029F2B5 /* KeyboardCommandStructs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardCommandStructs.swift; sourceTree = "<group>"; };
CE71705723A9C14D00A924A1 /* ResourceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceFileManager.swift; sourceTree = "<group>"; };
CE71705E23A9C97F00A924A1 /* PackageInstallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageInstallViewController.swift; sourceTree = "<group>"; };
@ -561,11 +557,9 @@
buildActionMask = 2147483647;
files = (
296EF2C72AFA26C700E3E384 /* ZIPFoundation.xcframework in Frameworks */,
CE5EDDC526522EAA001733AC /* XCGLogger.xcframework in Frameworks */,
CE5EDDD52652372A001733AC /* DeviceKit.xcframework in Frameworks */,
CE5EDDC726522EAA001733AC /* Sentry.xcframework in Frameworks */,
CE5EDDC826522EAA001733AC /* Reachability.xcframework in Frameworks */,
CE5EDDC926522EAA001733AC /* ObjcExceptionBridging.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -950,10 +944,8 @@
isa = PBXGroup;
children = (
296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */,
CE5EDDC426522EAA001733AC /* ObjcExceptionBridging.xcframework */,
CE5EDDC326522EAA001733AC /* Reachability.xcframework */,
CE5EDDC226522EAA001733AC /* Sentry.xcframework */,
CE5EDDC026522EA9001733AC /* XCGLogger.xcframework */,
CE5EDDBB26522EA3001733AC /* DeviceKit.xcframework */,
9A079DE52231A69D00581263 /* Foundation.framework */,
);
@ -1000,10 +992,10 @@
CE7A26DA23CEEF640005955C /* Colors.swift */,
CE7A26D723CEEC630005955C /* Colors+Extension.swift */,
C0959CD31F99C44E00B616BC /* Constants.swift */,
29B30C222B564F9900C342A4 /* KeymanEngineLogger.swift */,
CEE0321224C58C90005BFC73 /* KeymanHosts.swift */,
C0324B921F87689B00AF3785 /* KeymanURLProtocol.swift */,
C0A93A531F8B21240079948B /* Manager.swift */,
C05B14321FD914870082A316 /* Log.swift */,
CEA70CCE24CAC3AE001C12E6 /* Obsoletions.swift */,
CE9E95CC24CE786900F6DD78 /* UniversalLinks.swift */,
);
@ -1439,7 +1431,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C05B14331FD914870082A316 /* Log.swift in Sources */,
9A079E43223B61AE00581263 /* FullLexicalModelID.swift in Sources */,
C06D37341F81F5C300F61AE0 /* HTTPDownloader.swift in Sources */,
C0452BAF1F9F22A80064431A /* Font.swift in Sources */,
@ -1525,6 +1516,7 @@
C06085B41F9485E40057E5B9 /* UIButton+Helpers.swift in Sources */,
C0959CD41F99C44E00B616BC /* Constants.swift in Sources */,
C0452BAD1F9F21270064431A /* Keyboard.swift in Sources */,
29B30C232B564F9900C342A4 /* KeymanEngineLogger.swift in Sources */,
CE79B24923C711FF007E72AE /* KeyboardScaleMap.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View file

@ -9,6 +9,7 @@
import Foundation
import UIKit
import Reachability
import os.log
open class Alerts {
public typealias AcceptanceHandler = ((UIAlertAction)) -> Void
@ -48,8 +49,8 @@ open class Alerts {
do {
try networkReachable = Reachability(hostname: KeymanHosts.KEYMAN_COM.host!)
} catch {
log.debug("reachability could not start")
}
os_log("Reachability could not start", log:KeymanEngineLogger.ui, type: .debug)
}
if networkReachable?.connection == Reachability.Connection.unavailable || networkReachable == nil {
showConnectionErrorAlert(in: vc, handler: handler)

View file

@ -7,8 +7,8 @@
//
import Foundation
import XCGLogger
import Sentry
import os.log
/**
* This class centralizes the methods used among the KeymanEngine library, Keyman app, and SWKeyboard app-ex for Sentry-based
@ -26,10 +26,10 @@ public class SentryManager {
#if NO_SENTRY
// If doing development debugging (and NOT for Sentry code), silence Sentry reporting.
let allowEnabled = false
log.debug("Sentry error logging disabled for development mode.")
os_log("Sentry error logging disabled for development mode.", log: KeymanEngineLogger.settings, type: .debug)
#else
let allowEnabled = true
log.debug("Sentry error logging enabled.")
os_log("Sentry error logging enabled.", log: KeymanEngineLogger.settings, type: .debug)
#endif
let infoDict = Bundle(for: SentryManager.self).infoDictionary
@ -90,98 +90,55 @@ public class SentryManager {
#endif
}
private static func mapLoggingLevel(_ level: Sentry.SentryLevel) -> XCGLogger.Level {
switch(level) {
case .none:
return XCGLogger.Level.none
case .debug:
return .debug
case .info:
return .info
case .warning:
return .warning
case .error:
return .error
case .fatal:
return .severe
default:
return .info
}
}
/**
* Captures a Sentry event and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
* Captures a Sentry event and safely bypass the Sentry component if not activated by the app.
*/
public static func captureAndLog(_ event: Sentry.Event, logLevel: XCGLogger.Level? = nil) {
public static func capture(_ event: Sentry.Event) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
SentrySDK.capture(event: event)
}
let level = logLevel ?? mapLoggingLevel(event.level)
log.logln(event.message?.formatted, level: level)
}
/**
* Captures a Sentry event and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
* Captures a Sentry event for the specified error. If the logging level is not specified, it will default to .error.
* Will safely bypass the Sentry component if not activated by the app.
*/
public static func captureAndLog(_ error: Error, message: String? = nil, sentryLevel: Sentry.SentryLevel = .error, logLevel: XCGLogger.Level? = nil) {
public static func capture(_ error: Error, message: String? = nil, sentryLevel: Sentry.SentryLevel = .error) {
let event = Sentry.Event(error: error)
event.level = sentryLevel
if let message = message {
event.message = SentryMessage(formatted: message)
}
self.captureAndLog(event, logLevel: logLevel)
self.capture(event)
}
/**
* Constructs a SentryEvent around a message and also passes it to the engine's logging mechanism.
* If the logging level is not specified, it will default to .error.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
* Constructs a SentryEvent around a message. If the logging level is not specified, it will default to .error.
* Will safely bypass the Sentry component if not activated by the app.
*/
public static func captureAndLog(_ message: String, sentryLevel: Sentry.SentryLevel = .error, logLevel: XCGLogger.Level? = nil) {
public static func capture(_ message: String, sentryLevel: Sentry.SentryLevel = .error) {
let event = Sentry.Event(level: sentryLevel)
event.message = SentryMessage(formatted: message)
self.captureAndLog(event, logLevel: logLevel)
self.capture(event)
}
/**
* Adds a Sentry breadcrumb and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
* Adds a Sentry breadcrumb. Will safely bypass the Sentry component if not activated by the app.
*/
public static func breadcrumbAndLog(crumb: Sentry.Breadcrumb, logLevel: XCGLogger.Level? = nil) {
public static func breadcrumb(crumb: Sentry.Breadcrumb) {
// Guarded in case a library consumer decides against initializing Sentry.
if _started {
SentrySDK.addBreadcrumb(crumb)
}
let level = logLevel ?? mapLoggingLevel(crumb.level)
log.logln(crumb.message, level: level)
}
/**
* Adds a Sentry breadcrumb and copies its message to the engine's logging mechanism.
* If the logging level is not specified, the Sentry event's log-level will be used as a default.
*
* Will safely bypass the Sentry component if not activated by the app, only logging the
* message in such scenarios.
* Adds a Sentry breadcrumb. Will safely bypass the Sentry component if not activated by the app.
*/
public static func breadcrumbAndLog(_ message: String, category: String? = nil, sentryLevel: Sentry.SentryLevel = .info, logLevel: XCGLogger.Level? = nil) {
public static func breadcrumb(_ message: String, category: String? = nil, sentryLevel: Sentry.SentryLevel = .info) {
let crumb = Sentry.Breadcrumb()
crumb.level = sentryLevel
if let category = category {
@ -189,7 +146,7 @@ public class SentryManager {
}
crumb.message = message
self.breadcrumbAndLog(crumb: crumb, logLevel: logLevel)
self.breadcrumb(crumb: crumb)
}
public static func forceError() {

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
public extension UserDefaults {
func installableKeyboards(forKey key: String) -> [InstallableKeyboard]? {
@ -18,7 +19,9 @@ public extension UserDefaults {
do {
return try array.map { try decoder.decode(InstallableKeyboard.self, from: $0) }
} catch {
SentryManager.captureAndLog(error, message: "Error decoding keyboards: \(error)")
let message = "Error decoding keyboards: \(error))"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
return nil
}
}
@ -31,7 +34,9 @@ public extension UserDefaults {
do {
return try array.map { try decoder.decode(InstallableLexicalModel.self, from: $0) }
} catch {
SentryManager.captureAndLog(error, message: "Error decoding lexical models: \(error)")
let message = "Error decoding lexical models: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
return nil
}
}
@ -61,7 +66,9 @@ public extension UserDefaults {
let array = try keyboards.map { try encoder.encode($0) }
set(array, forKey: key)
} catch {
SentryManager.captureAndLog(error, message: "Error encoding keyboards: \(error)")
let message = "Error encoding keyboards: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -75,7 +82,9 @@ public extension UserDefaults {
let array = try lexicalModels.map { try encoder.encode($0) }
set(array, forKey: key)
} catch {
SentryManager.captureAndLog(error, message: "Error encoding lexicalModels: \(error)")
let message = "Error encoding lexicalModels: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -86,7 +95,9 @@ public extension UserDefaults {
do {
return try PropertyListDecoder().decode(FullKeyboardID.self, from: data)
} catch {
SentryManager.captureAndLog(error, message: "Error decoding FullKeyboardID: \(error)")
let message = "Error decoding FullKeyboardID: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
return nil
}
}
@ -100,7 +111,9 @@ public extension UserDefaults {
let data = try PropertyListEncoder().encode(id)
set(data, forKey: key)
} catch {
SentryManager.captureAndLog(error, message: "Error encoding FullKeyboardID: \(error)")
let message = "Error encoding FullKeyboardID: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -111,7 +124,9 @@ public extension UserDefaults {
do {
return try PropertyListDecoder().decode(FullLexicalModelID.self, from: data)
} catch {
SentryManager.captureAndLog(error, message: "Error decoding FullLexicalModelID: \(error)")
let message = "Error decoding FullLexicalModelID: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
return nil
}
}
@ -125,7 +140,9 @@ public extension UserDefaults {
let data = try PropertyListEncoder().encode(id)
set(data, forKey: key)
} catch {
SentryManager.captureAndLog(error, message: "Error encoding FullLexicalModelID: \(error)")
let message = "Error encoding FullLexicalModelID: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message: message)
}
}

View file

@ -7,8 +7,9 @@
//
import Foundation
import os.log
protocol HTTPDownloadDelegate: class {
protocol HTTPDownloadDelegate: AnyObject {
func downloadRequestStarted(_ request: HTTPDownloadRequest)
func downloadRequestFinished(_ request: HTTPDownloadRequest)
func downloadRequestFailed(_ request: HTTPDownloadRequest, with: Error?)
@ -99,8 +100,8 @@ class HTTPDownloader: NSObject {
}
// Successful download.
log.debug("Downloaded file \(currentRequest.url) as \(location), " +
"to be copied to \(currentRequest.destinationFile ?? "nil")")
let message = "Downloaded file \(currentRequest.url) as \(location), to be copied to \(currentRequest.destinationFile ?? "nil")"
os_log("%{public}s", log: KeymanEngineLogger.resources, type: .debug, message)
// If a destination file for the download has already been specified, let's go ahead and copy it over.
if let destFile = currentRequest.destinationFile {
@ -115,7 +116,7 @@ class HTTPDownloader: NSObject {
try FileManager.default.copyItem(at: location,
to: destFileUrl)
} catch {
log.error("Error saving the download: \(error)")
os_log("Error saving the download: %{public}s", log: KeymanEngineLogger.resources, type: .error, error.localizedDescription)
}
}

View file

@ -9,6 +9,7 @@
import Foundation
import WebKit
import DeviceKit
import os.log
public class PackageInstallViewController<Resource: LanguageResource>: UIViewController, UITableViewDelegate, UITableViewDataSource, UITabBarControllerDelegate, UIAdaptivePresentationControllerDelegate {
private enum NavigationMode: Int { // b/c hashable
@ -97,7 +98,7 @@ public class PackageInstallViewController<Resource: LanguageResource>: UIViewCon
var preinstalleds: Set<String> = Set()
guard let typedPackage = package as? TypedKeymanPackage<Resource> else {
log.warning("Cannot check for previously-installed resources of unexpected type")
os_log("Cannot check for previously-installed resources of unexpected type", log:KeymanEngineLogger.resources, type: .error)
return preinstalleds
}

View file

@ -8,6 +8,7 @@
import AudioToolbox
import UIKit
import os.log
public enum GlobeKeyTapBehaviour {
case switchToNextKeyboard
@ -367,7 +368,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
// This should allow us to debug any failures of this assumption.
// So far, only occurs when debugging a breakpoint during a touch event on BKSP,
// so all seems good.
log.verbose("Failed to swallow a recent textDidChange call!")
os_log("Failed to swallow a recent textDidChange call!", log: KeymanEngineLogger.ui, type: .default)
}
self.swallowBackspaceTextChange = true
break
@ -538,7 +539,9 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
func clearText() {
setContextState(text: nil, range: NSRange(location: 0, length: 0))
keymanWeb.resetContext()
SentryManager.breadcrumbAndLog("Cleared text.")
let message = "Cleared text."
os_log("%{public}s", log: KeymanEngineLogger.ui, type: .info, message)
SentryManager.breadcrumb(message)
}
func resetContext() {

View file

@ -9,6 +9,7 @@
import Foundation
import DeviceKit
import UIKit
import os.log
/**
* Documents the basic size properties of the default system keyboard, _as seen from within a `UIInputView` or its `UIInputViewController`_.
@ -168,13 +169,12 @@ class KeyboardScaleMap {
* the largest device smaller than or equal to the detected dimensions.
*/
private static func getUnknownDeviceMapping(screenSize _screenSize: CGSize = UIScreen.main.bounds.size, asPhone: Bool? = nil) -> Device {
// Shouldn't happen, but just in case.
if _screenSize == CGSize.zero {
// // This would notify us whenever new devices are out that we haven't build a mapping for.
// SentryManager.captureAndLog("Cannot detect device dimensions; defaulting to smallest device for form factor.", sentryLevel: .info)
// We haven't actually updated things here in a while, so we'll just breadcrumb for now.
SentryManager.breadcrumbAndLog("Cannot detect device dimensions; defaulting to smallest device for form factor.", sentryLevel: .error)
// Shouldn't happen, but just in case.
if _screenSize == CGSize.zero {
// This would notify us whenever new devices are out that we haven't build a mapping for.
let message = "Cannot detect device dimensions; defaulting to smallest device for form factor."
os_log("%{public}s", log: KeymanEngineLogger.ui, type: .error, message)
SentryManager.capture(message, sentryLevel: .error)
}
// Convert to CGSize in portrait orientation.
@ -228,7 +228,7 @@ class KeyboardScaleMap {
default:
if !isUnknown {
// We can still perform a mapping, but it's not ideal.
log.warning("Keyboard scaling definition missing for device \(device.description)")
os_log("Keyboard scaling definition missing for device %{public}s", log: KeymanEngineLogger.ui, type: .default, device.description)
}
// The expected case: isUnknown = true.

View file

@ -10,7 +10,7 @@ import UIKit
import WebKit
import AudioToolbox
import Sentry
import XCGLogger
import os.log
private let keyboardChangeHelpText = NSLocalizedString("keyboard-help-change", bundle: engineBundle, comment: "")
@ -195,7 +195,9 @@ extension KeymanWebViewController {
let cmd = "executePopupKey(\"\(id)\",\"\(escapedText)\");"
webView!.evaluateJavaScript(cmd, completionHandler: nil)
} catch {
SentryManager.captureAndLog(error)
let message = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .error, message)
SentryManager.capture(error, message: message)
return
}
}
@ -246,7 +248,8 @@ extension KeymanWebViewController {
self.currentText = String(jsonText)
webView!.evaluateJavaScript("setKeymanVal(\"\(jsonText)\");", completionHandler: nil)
} catch {
SentryManager.captureAndLog(error.localizedDescription)
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .error, error.localizedDescription)
SentryManager.capture(error.localizedDescription)
}
}
@ -288,12 +291,14 @@ extension KeymanWebViewController {
// failed to initialize properly.
guard FileManager.default.fileExists(atPath: fileURL.path) else {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "File missing for keyboard")
let errorMessage = "File missing for keyboard"
event.message = SentryMessage(formatted: errorMessage)
event.extra = [ "id": keyboard.id, "file": fileURL ]
if let packageID = keyboard.packageID {
event.extra?["package"] = packageID
}
SentryManager.captureAndLog(event)
SentryManager.capture(event)
os_log("%{public}s id: %s file: %{public}s", log: KeymanEngineLogger.resources, type: .error, errorMessage, keyboard.id, fileURL as CVarArg)
throw KeyboardError.fileMissing
}
@ -311,12 +316,14 @@ extension KeymanWebViewController {
data = try JSONSerialization.data(withJSONObject: stub, options: [])
} catch {
let event = Sentry.Event(error: error)
let errorMessage = "Failed to serialize keyboard stub:"
event.message = SentryMessage(formatted: "Failed to serialize keyboard stub: \(error)")
event.extra = [:]
event.extra!["id"] = stub["KI"]
event.extra!["package"] = stub["KP"]
SentryManager.captureAndLog(event)
SentryManager.capture(event)
os_log("%{public}s id: %{public}s file: %{public}s", log: KeymanEngineLogger.resources, type: .error, errorMessage, keyboard.id, fileURL as CVarArg)
throw KeyboardError.keyboardLoadingError
}
guard let stubString = String(data: data, encoding: .utf8) else {
@ -326,12 +333,13 @@ extension KeymanWebViewController {
event.extra!["id"] = stub["KI"]
event.extra!["package"] = stub["KP"]
SentryManager.captureAndLog(event)
os_log("Failed to create keyboard stub string for embedded KMW", log: KeymanEngineLogger.ui, type: .error)
SentryManager.capture(event)
throw KeyboardError.keyboardLoadingError
}
SentryManager.breadcrumbAndLog("Keyboard stub built for \(keyboard.id)", logLevel: XCGLogger.Level.none)
log.info("Keyboard stub: \(stubString)")
let message = "Keyboard stub built for \(keyboard.id)"
os_log("%{public}s", log: KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
webView!.evaluateJavaScript("setKeymanLanguage(\(stubString));", completionHandler: nil)
}
@ -349,12 +357,15 @@ extension KeymanWebViewController {
guard FileManager.default.fileExists(atPath: fileURL.path) else {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "File missing for lexical model")
let errorMessage = "File missing for lexical model"
event.message = SentryMessage(formatted: errorMessage)
event.extra = [ "id": lexicalModel.id, "file": fileURL ]
if let packageID = lexicalModel.packageID {
event.extra?["package"] = packageID
}
SentryManager.captureAndLog(event)
os_log("%{public}s: %{public}s", log: KeymanEngineLogger.resources, type: .error, errorMessage, lexicalModel.id)
SentryManager.capture(event)
throw KeyboardError.fileMissing
}
@ -363,25 +374,30 @@ extension KeymanWebViewController {
data = try JSONSerialization.data(withJSONObject: stub, options: [])
} catch {
let event = Sentry.Event(error: error)
let errorMessage = "Failed to serialize lexical model stub:"
event.message = SentryMessage(formatted: "Failed to serialize lexical model stub: \(error)")
event.extra = [:]
event.extra!["id"] = stub["id"]
SentryManager.captureAndLog(event)
os_log("%{public}s: %{public}s", log: KeymanEngineLogger.resources, type: .error, errorMessage, error.localizedDescription)
SentryManager.capture(event)
throw KeyboardError.lexicalModelLoadingError
}
guard let stubString = String(data: data, encoding: .utf8) else {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "Failed to create lexical model stub string for embedded KMW")
let errorMessage = "Failed to create lexical model stub string for embedded KMW"
event.message = SentryMessage(formatted: errorMessage)
event.extra = [:]
event.extra!["id"] = stub["id"]
SentryManager.captureAndLog(event)
os_log("%{public}s", log: KeymanEngineLogger.resources, type: .error, errorMessage)
SentryManager.capture(event)
throw KeyboardError.lexicalModelLoadingError
}
SentryManager.breadcrumbAndLog("LexicalModel stub built for \(lexicalModel.id)", logLevel: XCGLogger.Level.none)
log.info("LexicalModel stub: \(stubString)")
let message = "LexicalModel stub built for \(lexicalModel.id)"
os_log("%{public}s", log: KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
if lexicalModel.languageID == Manager.shared.currentKeyboardID?.languageID {
// We're registering a lexical model for the now-current keyboard.
@ -404,7 +420,9 @@ extension KeymanWebViewController {
}
func showBanner(_ display: Bool) {
SentryManager.breadcrumbAndLog("Changing banner's alwaysShow property to \(display).", category: "engine", sentryLevel: .debug)
let message = "Changing banner's alwaysShow property to \(display)"
os_log("%{public}s", log: KeymanEngineLogger.settings, type: .debug, message)
SentryManager.breadcrumb(message, category: "engine", sentryLevel: .debug)
webView?.evaluateJavaScript("showBanner(\(display ? "true" : "false"))", completionHandler: nil)
}
@ -416,7 +434,8 @@ extension KeymanWebViewController {
} else {
logString = path
}
log.debug("Banner image path: '\(logString).'")
let message = "Banner image path: '\(logString).'"
os_log("%{public}s", log: KeymanEngineLogger.ui, type: .debug, message)
webView?.evaluateJavaScript("setBannerImage(\"\(path)\");", completionHandler: nil)
}
@ -519,7 +538,8 @@ extension KeymanWebViewController: WKScriptMessageHandler {
subkeyIDs.append(values[0])
subkeyTexts.append(values[1].stringFromUTF16CodeUnits() ?? "")
default:
log.warning("Unexpected subkey key: \(key)")
let message = "Unexpected subkey key: \(key)"
os_log("%{public}s", log: KeymanEngineLogger.ui, type: .error, message)
}
}
@ -552,7 +572,10 @@ extension KeymanWebViewController: WKScriptMessageHandler {
// This may need filtering for proper use with Sentry?
// Then again, if KMW is logging it... we already have to worry
// about it showing up in Web-oriented Sentry logs.
SentryManager.breadcrumbAndLog("KMW Log: \(message)", category: "engine")
let logMessage = "KMW Log: \(message)"
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, logMessage)
SentryManager.breadcrumb(logMessage)
} else if fragment.hasPrefix("#beep-") {
beep(self)
delegate?.beep(self)
@ -565,9 +588,12 @@ extension KeymanWebViewController: WKScriptMessageHandler {
do {
let cmd = try decoder.decode(SuggestionPopup.self, from: cmdData!)
log.verbose("Longpress detected on suggestion: \"\(cmd.suggestion.displayAs)\".")
} catch {
SentryManager.captureAndLog(error, message: "Unexpected JSON parse error: \(error).")
let message = "Longpress detected on suggestion: \"\(cmd.suggestion.displayAs)\"."
os_log("%{public}s", log: KeymanEngineLogger.ui, type: .debug, message)
} catch {
let message = "Unexpected JSON parse error: \(error)."
os_log("%{public}s", log:KeymanEngineLogger.engine, type: .error, message)
SentryManager.capture(error, message: message)
}
// Will need processing upon extraction from the resulting object.
@ -579,7 +605,9 @@ extension KeymanWebViewController: WKScriptMessageHandler {
// let frame = KeymanWebViewController.keyFrame(x: x, y: y, w: w, h: h)
} else {
SentryManager.captureAndLog("Unexpected KMW event: \(fragment)")
let message = "Unexpected KMW event: \(fragment)"
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, message)
SentryManager.capture(message)
}
}
@ -636,7 +664,9 @@ extension KeymanWebViewController: KeymanWebDelegate {
delegate?.keyboardLoaded(keymanWeb)
isLoading = false
SentryManager.breadcrumbAndLog("Loaded keyboard.", sentryLevel: .debug, logLevel: .info)
let message = "Loaded keyboard."
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, message)
SentryManager.breadcrumb(message, sentryLevel: .debug)
self.setSentryState()
resizeKeyboard()
@ -663,7 +693,9 @@ extension KeymanWebViewController: KeymanWebDelegate {
newKb = userKbs[0]
}
}
SentryManager.breadcrumbAndLog("Setting initial keyboard.", category: "engine")
let message = "Setting initial keyboard."
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, message)
SentryManager.breadcrumb(message)
// Compare against resetKeyboard & Manager.setKeyboard;
// setting this to `nil` allows us to force keyboard reloads when needed.
@ -688,7 +720,9 @@ extension KeymanWebViewController: KeymanWebDelegate {
_ = Manager.shared.setKeyboard(Defaults.keyboard)
}
} catch {
SentryManager.captureAndLog("Could not load default keyboard as a fallback for keyboard loading failure", sentryLevel: .fatal)
let message = "Could not load default keyboard as a fallback for keyboard loading failure"
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .error, message)
SentryManager.capture(message, sentryLevel: .fatal)
}
newKb = Defaults.keyboard
}
@ -1066,13 +1100,19 @@ extension KeymanWebViewController {
Manager.shared.currentKeyboardID = nil
if let keyboard = keyboard {
SentryManager.breadcrumbAndLog("Current keyboard is set.", category: "engine")
let message = "Current keyboard is set."
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, message)
SentryManager.breadcrumb(message)
_ = Manager.shared.setKeyboard(keyboard)
} else if let keyboard = Storage.active.userDefaults.userKeyboards?[safe: 0] {
SentryManager.breadcrumbAndLog("Using user's default keyboard.", category: "engine")
let message = "Using user's default keyboard."
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, message)
SentryManager.breadcrumb(message)
_ = Manager.shared.setKeyboard(keyboard)
} else {
SentryManager.breadcrumbAndLog("Using app-default keyboard.", category: "engine")
let message = "Using app-default keyboard."
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .info, message)
SentryManager.breadcrumb(message)
_ = Manager.shared.setKeyboard(Defaults.keyboard)
}
}

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
public class KeyboardKeymanPackage : TypedKeymanPackage<InstallableKeyboard> {
internal var keyboards: [KMPKeyboard]!
@ -22,7 +23,9 @@ public class KeyboardKeymanPackage : TypedKeymanPackage<InstallableKeyboard> {
if(keyboard.isValid && FileManager.default.fileExists(atPath: self.sourceFolder.appendingPathComponent("\(keyboard.keyboardId).js").path)) {
keyboards.append(keyboard)
} else {
SentryManager.breadcrumbAndLog("\(keyboard.name) not valid / corresponding file not found")
let message = "\(keyboard.name) not valid / corresponding file not found"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
}
}
}

View file

@ -0,0 +1,35 @@
/**
* Keyman is copyright (C) SIL International. MIT License.
*
* KeymanEngineLogger.swift
* KeymanEngine
*
* Created by Shawn Schantz on 2024-01-16.
*
* Basic logging capability provided by wrapping Apple's Unified Logging
* Framework. Works with iOS 10 and later.
* Log statements are written with a call the `os_log` API which takes a
* message, an `OSLog` reference and log level.
* Several static references to `OSLog` objects are defined, each of which
* determines the subsystem and category of subsequent log statements.
* Both subsystem and category are useful for filtering with the Console app.
* Use the same subsystem name for all `OSLog` objects created here, but specify
* a unique category to identify related functionality.
* Add as many static `OSLog` resources as necessary to support new log
* categories.
*/
import Foundation
import os.log
// bundle for engine is "org.sil.Keyman.ios.Engine"
// but selecting this subsystem name for consistency across apps
private let subsystem = "com.keyman.ios.engine"
public struct KeymanEngineLogger {
static let engine = OSLog(subsystem: subsystem, category: "Engine")
static let migration = OSLog(subsystem: subsystem, category: "Migration")
static let resources = OSLog(subsystem: subsystem, category: "Resources")
static let settings = OSLog(subsystem: subsystem, category: "Settings")
static let ui = OSLog(subsystem: subsystem, category: "UI")
}

View file

@ -8,6 +8,7 @@
import Foundation
import ZIPFoundation
import os.log
// KMPErrors may be passed to UIAlertControllers, so they need localization.
public enum KMPError : String, Error {
@ -245,7 +246,7 @@ public class KeymanPackage {
try FileManager.default.removeItem(at: self.sourceFolder)
}
} catch {
log.debug("Could not remove temporary extraction site on package deinit")
os_log("Could not remove temporary extraction site on package deinit", log:KeymanEngineLogger.resources, type: .error)
}
}
}
@ -436,7 +437,8 @@ public class KeymanPackage {
let minVersion = Version(minSupportedVersion)
if (currentVersion.major < minVersion!.major) {
log.error("package '\(packageName)' could not be installed because it requires version \(minSupportedVersion) of Keyman")
let message = "package '\(packageName)' could not be installed because it requires version \(minSupportedVersion) of Keyman"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
throw KMPError.unsupportedKeymanVersion
}
}
@ -449,7 +451,9 @@ public class KeymanPackage {
let package = try KeymanPackage.parse(destination)
complete(package)
} catch {
SentryManager.captureAndLog(error, sentryLevel: .info)
let message = "Keyboard installation error: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(error, message: message, sentryLevel: .info)
complete(nil)
}
}

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
class KeymanURLProtocol: URLProtocol, NSURLConnectionDataDelegate {
private static let protocolHandledKey = "KMURLProtocolHandledKey"
@ -32,7 +33,7 @@ class KeymanURLProtocol: URLProtocol, NSURLConnectionDataDelegate {
override func startLoading() {
guard let mutableRequest = request as? NSMutableURLRequest else {
log.error("Bridge to NSMutableURLRequest failed")
os_log("Bridge to NSMutableURLRequest failed", log:KeymanEngineLogger.resources, type: .error)
return
}
URLProtocol.setProperty(true, forKey: KeymanURLProtocol.protocolHandledKey, in: mutableRequest)

View file

@ -8,6 +8,7 @@
import UIKit
import os.log
private let toolbarButtonTag = 100
private let toolbarLabelTag = 101
@ -53,19 +54,19 @@ class LanguageLMDetailViewController: UITableViewController, UIAlertViewDelegate
forName: Notifications.packageDownloadFailed,
observer: self,
function: LanguageLMDetailViewController.lexicalModelDownloadFailed)
log.info("viewDidLoad: LanguageLMDetailViewController (registered for lexicalModelDownloadStarted)")
os_log("viewDidLoad: LanguageLMDetailViewController (registered for lexicalModelDownloadStarted)", log:KeymanEngineLogger.ui, type: .info)
}
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
log.info("willAppear: LanguageLMDetailViewController")
os_log("viewWillAppear: LanguageLMDetailViewController", log:KeymanEngineLogger.ui, type: .info)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
log.info("didAppear: LanguageLMDetailViewController")
os_log("viewDidAppear: LanguageLMDetailViewController", log:KeymanEngineLogger.ui, type: .info)
navigationController?.setToolbarHidden(true, animated: true)
}
@ -156,7 +157,7 @@ class LanguageLMDetailViewController: UITableViewController, UIAlertViewDelegate
}
private func lexicalModelDownloadStarted() {
log.info("lexicalModelDownloadStarted: LanguageLMDetailViewController")
os_log("lexicalModelDownloadStarted: LanguageLMDetailViewController", log:KeymanEngineLogger.resources, type: .info)
view.isUserInteractionEnabled = false
navigationItem.setHidesBackButton(true, animated: true)
@ -164,8 +165,8 @@ class LanguageLMDetailViewController: UITableViewController, UIAlertViewDelegate
}
private func lexicalModelDownloadFailed() {
log.info("lexicalModelDownloadFailed: LanguageLMDetailViewController")
view.isUserInteractionEnabled = true
os_log("lexicalModelDownloadFailed: LanguageLMDetailViewController", log:KeymanEngineLogger.resources, type: .info)
view.isUserInteractionEnabled = true
navigationItem.setHidesBackButton(false, animated: true)
}

View file

@ -9,6 +9,7 @@
// It replaces the original in every case but one.
import UIKit
import os.log
private let toolbarButtonTag = 100
@ -27,7 +28,7 @@ class KeyboardSwitcherViewController: UITableViewController, UIAlertViewDelegate
//NEEDED?
navigationItem.rightBarButtonItem = nil
log.info("didLoad: KeyboardSwitcherViewController")
os_log("didLoad: KeyboardSwitcherViewController", log: KeymanEngineLogger.ui, type: .info)
}
override func viewWillAppear(_ animated: Bool) {

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
public class LexicalModelKeymanPackage : TypedKeymanPackage<InstallableLexicalModel> {
internal var models : [KMPLexicalModel]!
@ -24,7 +25,9 @@ public class LexicalModelKeymanPackage : TypedKeymanPackage<InstallableLexicalMo
if(model.isValid && FileManager.default.fileExists(atPath: self.sourceFolder.appendingPathComponent("\(model.lexicalModelId).model.js").path)) {
models.append(model)
} else {
SentryManager.breadcrumbAndLog("\(model.name) not valid / corresponding file not found")
let message = "\(model.name) not valid / corresponding file not found"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
}
}
}

View file

@ -8,6 +8,7 @@
import Foundation
import UIKit
import os.log
private let toolbarButtonTag = 100
private let toolbarLabelTag = 101
@ -57,7 +58,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
observer: self,
function: LexicalModelPickerViewController.lexicalModelDownloadFailed)
log.info("didLoad: LexicalModelPickerViewController (registered lexicalModelDownloadCompleted et al)")
os_log("viewDidLoad: LexicalModelPickerViewController (registered lexicalModelDownloadCompleted et al)", log:KeymanEngineLogger.ui, type: .info)
}
override func viewWillAppear(_ animated: Bool) {
@ -69,8 +70,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
log.info("didAppear: LexicalModelPickerViewController")
os_log("viewDidAppear: LexicalModelPickerViewController", log:KeymanEngineLogger.ui, type: .info)
scroll(toSelectedLexicalModel: false)
}
@ -176,8 +177,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
private func lexicalModelDownloadCompleted() {
log.info("lexicalModelDownloadCompleted LexicalModelPicker")
os_log("lexicalModelDownloadCompleted LexicalModelPicker", log:KeymanEngineLogger.ui, type: .info)
// Actually used now.
view.isUserInteractionEnabled = true
navigationItem.leftBarButtonItem?.isEnabled = true
@ -286,7 +287,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
//get list of lexical models for this languageID and show it
Queries.LexicalModel.fetch(forLanguageCode: language.id) { result, error in
if let error = error {
log.info("Failed to fetch lexical model list for "+self.language.id+". error: "+error.localizedDescription)
let errorMessage = "Failed to fetch lexical model list for \(self.language.id). Error: \(error.localizedDescription)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, errorMessage)
DispatchQueue.main.async {
self.lexicalModelDownloadFailed(PackageDownloadFailedNotification(packageKey: nil, error: error))
}
@ -301,7 +303,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
if result.count == 0 {
self.noModelsAvailable(cause: "empty")
} else {
log.info("Fetched lexical model list for "+self.language.id+".")
let message = "Fetched lexical model list for \(self.language.id)."
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
let packages: [(InstallableLexicalModel, URL)] = result.map { ($0.modelFor(languageID: self.language.id)!, URL.init(string: $0.packageFilename)!) }
// show the list of lexical models (on the main thread)
DispatchQueue.main.async {
@ -317,11 +320,11 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
func noModelsAvailable(cause: String = "nil") {
let msg = NSLocalizedString("menu-lexical-model-none-message", bundle: engineBundle, comment: "")
let logMsg = "No lexical models available for language \(language.id): (\(cause))"
log.info(logMsg)
let message = NSLocalizedString("menu-lexical-model-none-message", bundle: engineBundle, comment: "")
let logMessage = "No lexical models available for language \(language.id): (\(cause))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, logMessage)
let alertController = UIAlertController(title: title, message: msg,
let alertController = UIAlertController(title: title, message: message,
preferredStyle: UIAlertController.Style.alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("command-ok", bundle: engineBundle, comment: ""),
style: UIAlertAction.Style.default,

View file

@ -1,34 +0,0 @@
//
// Log.swift
// KeymanEngine
//
// Created by Gabriel Wong on 2017-12-07.
// Copyright © 2017 SIL International. All rights reserved.
//
import XCGLogger
// From XCGLogger docs:
// Note: This creates the log object lazily, which means it's not created until it's actually needed.
public let log: XCGLogger = {
// Default: the 'console', which is read by Xcode but doesn't reach the system logs.
let mainLog = XCGLogger(identifier: "KeymanEngine", includeDefaultDestinations: false)
// Ensures our log messages go out to the device's system log as well as the console.
let systemLogDest = AppleSystemLogDestination(identifier: "")
systemLogDest.showLogIdentifier = true
mainLog.add(destination: systemLogDest)
// Temporary logging level to ensure that app details are reported properly.
mainLog.outputLevel = .info
mainLog.logAppDetails()
#if DEBUG
mainLog.outputLevel = .debug
#else
mainLog.outputLevel = .warning
#endif
return mainLog
}()

View file

@ -8,9 +8,9 @@
import UIKit
import WebKit
import XCGLogger
import DeviceKit
import Reachability
import os.log
typealias FetchKeyboardsBlock = ([String: Any]?) -> Void
@ -211,7 +211,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
do {
try Storage.active.copyKMWFiles(from: Resources.bundle)
} catch {
SentryManager.captureAndLog(error, message: "Failed to copy KMW files from bundle: \(error)")
let message = ("Failed to copy KMW files from bundle: \(error)")
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message:message)
}
updateUserKeyboards(with: Defaults.keyboard)
@ -219,7 +221,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
do {
try reachability = Reachability(hostname: KeymanHosts.API_KEYMAN_COM.host!)
} catch {
SentryManager.captureAndLog(error, message: "Could not start Reachability object: \(error)")
let message = ("Could not start Reachability object: \(error)")
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message:message)
}
if(!Util.isSystemKeyboard) {
@ -228,7 +232,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
do {
try reachability.startNotifier()
} catch {
SentryManager.captureAndLog(error, message: "failed to start Reachability notifier: \(error)")
let message = ("failed to start Reachability notifier: \(error)")
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message:message)
}
}
@ -294,11 +300,15 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
// We MUST NOT shortcut this method as a result; doing so may (rarely) result in the infamous
// blank keyboard bug!
if kb.fullID == currentKeyboardID && !self.isSystemKeyboard && !inputViewController.shouldReload {
SentryManager.breadcrumbAndLog("Keyboard unchanged: \(kb.fullID)")
let message = "Keyboard unchanged: \(kb.fullID)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
return false
}
SentryManager.breadcrumbAndLog("Setting language: \(kb.fullID)")
let message = "Setting language: \(kb.fullID)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb("Setting language: \(kb.fullID)")
currentKeyboardID = kb.fullID
@ -361,7 +371,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
if !Migrations.resourceHasPackageMetadata(keyboard) {
let wrappedKbds = Migrations.migrateToKMPFormat([keyboard])
guard wrappedKbds.count == 1 else {
SentryManager.captureAndLog("Could not properly import keyboard")
let message = "Could not properly import keyboard"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
return
}
kbdToInstall = wrappedKbds[0]
@ -398,7 +410,8 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
try ResourceFileManager.shared.install(resourceWithID: fullId, from: package)
}
catch {
log.error("Could not add lexical model for id '\(lexicalModelId)' and languageId '\(languageId)' due to error: \(error)")
let message = "Could not add lexical model for id '\(lexicalModelId)' and languageId '\(languageId)' due to error: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
}
if let lexicalModel = Storage.active.userDefaults.userLexicalModel(withFullID: fullId) {
@ -421,7 +434,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
/// Registers a lexical model with KMW.
public func registerLexicalModel(_ lm: InstallableLexicalModel) -> Bool {
SentryManager.breadcrumbAndLog("Setting lexical model: \(lm.fullID)")
let message = "Setting lexical model: \(lm.fullID)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
currentLexicalModelID = lm.fullID
@ -462,7 +477,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
if !Migrations.resourceHasPackageMetadata(lexicalModel) {
let wrappedModels = Migrations.migrateToKMPFormat([lexicalModel])
guard wrappedModels.count == 1 else {
SentryManager.captureAndLog("Could not properly import lexical model")
let message = "Could not properly import lexical model"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.capture(message)
return
}
modelToInstall = wrappedModels[0]
@ -502,7 +519,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
userData.set([Date()], forKey: Key.synchronizeSWKeyboard)
userData.synchronize()
SentryManager.breadcrumbAndLog("Removing keyboard with ID \(kb.id) and languageID \(kb.languageID)")
let message = ("Removing keyboard with ID \(kb.id) and languageID \(kb.languageID)")
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
// Set a new keyboard if deleting the current one
if kb.fullID == currentKeyboardID {
@ -516,12 +535,18 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
// rather than just 'no matching keyboards'.
let keyboardDir = Storage.active.resourceDir(for: kb)!
FontManager.shared.unregisterFonts(in: keyboardDir, fromSystemOnly: false)
SentryManager.breadcrumbAndLog("Deleting directory \(keyboardDir)")
let message = "Deleting directory \(keyboardDir)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
if (try? FileManager.default.removeItem(at: keyboardDir)) == nil {
SentryManager.captureAndLog("Failed to delete \(keyboardDir) when removing keyboard")
let message = "Failed to delete \(keyboardDir) when removing keyboard"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.capture(message)
}
} else {
SentryManager.breadcrumbAndLog("User has another language installed. Skipping delete of keyboard files.")
let message = "User has another language installed. Skipping delete of keyboard files."
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
}
NotificationCenter.default.post(name: Notifications.keyboardRemoved, object: self, value: kb)
@ -570,7 +595,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
}
let lm = userLexicalModels[index]
SentryManager.breadcrumbAndLog("Removing lexical model with ID \(lm.id) and languageID \(lm.languageID) from user list of all models")
let message = "Removing lexical model with ID \(lm.id) and languageID \(lm.languageID) from user list of all models"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
userLexicalModels.remove(at: index)
ud.userLexicalModels = userLexicalModels
ud.set([Date()], forKey: Key.synchronizeSWLexicalModel)
@ -580,7 +607,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
/// Removes the lexical model at index from the lexical models list if it exists.
public func removeLexicalModelFromLanguagePreference(userDefs ud: UserDefaults, _ lm: InstallableLexicalModel) {
SentryManager.breadcrumbAndLog("Removing lexical model with ID \(lm.id) and languageID \(lm.languageID) from per-language prefs")
let message = "Removing lexical model with ID \(lm.id) and languageID \(lm.languageID) from per-language prefs"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
ud.set(preferredLexicalModelID: nil, forKey: lm.languageID)
}
@ -601,7 +630,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
if let first_lm = userLexicalModels.first(where: {$0.languageID == lm.languageID}) {
_ = registerLexicalModel(first_lm)
} else {
SentryManager.breadcrumbAndLog("no more lexical models available for language \(lm.fullID)")
let message = "no more lexical models available for language \(lm.fullID)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
currentLexicalModelID = nil
}
}
@ -609,12 +640,18 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
if !userLexicalModels.contains(where: { $0.id == lm.id }) {
let lexicalModelDir = Storage.active.resourceDir(for: lm)!
FontManager.shared.unregisterFonts(in: lexicalModelDir, fromSystemOnly: false)
SentryManager.breadcrumbAndLog("Deleting directory \(lexicalModelDir)")
let message = "Deleting directory \(lexicalModelDir)"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
if (try? FileManager.default.removeItem(at: lexicalModelDir)) == nil {
SentryManager.captureAndLog("Failed to delete \(lexicalModelDir) when removing lexical model")
let message = "Failed to delete \(lexicalModelDir) when removing lexical model"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.capture(message)
}
} else {
SentryManager.breadcrumbAndLog("User has another language installed. Skipping delete of lexical model files.")
let message = "User has another language installed. Skipping delete of lexical model files."
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
}
NotificationCenter.default.post(name: Notifications.lexicalModelRemoved, object: self, value: lm)
@ -706,7 +743,7 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
}
@objc func reachabilityChanged(_ notification: Notification) {
log.debug {
let message = { [self] in
let reachStr: String
switch reachability.connection {
case Reachability.Connection.wifi:
@ -718,6 +755,7 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
}
return "Reachability changed to '\(reachStr)'"
}
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message())
}
// MARK: - Loading custom keyboards
@ -793,7 +831,9 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
try shared.copyFiles(to: nonShared)
FontManager.shared.registerCustomFonts()
} catch {
SentryManager.captureAndLog(error, message: "Failed to copy from shared container: \(error)")
let message = ("Failed to copy from shared container: \(error)")
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(error, message:message)
}
}
}

View file

@ -8,6 +8,7 @@
import Foundation
import Sentry
import os.log
private let userInfoKey = "value"
@ -42,7 +43,9 @@ public extension NotificationCenter {
if let value = notification.userInfo?[userInfoKey] as? T {
block(value)
} else {
SentryManager.captureAndLog("Unexpected userInfo in notification: \(String(describing: notification))")
let message = "Unexpected userInfo in notification: \(String(describing: notification))"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.capture(message)
}
}
return NotificationObserver(observer: observer, center: self)

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
extension Queries {
public class LexicalModel {
@ -56,7 +57,9 @@ extension Queries {
var urlComponents = MODEL_ENDPOINT
urlComponents.queryItems = [URLQueryItem(name: "q", value: "bcp47:\(bcp47)")]
SentryManager.breadcrumbAndLog("Querying package versions through API endpoint: \(urlComponents.url!)")
let message = "Querying package versions through API endpoint: \(urlComponents.url!)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
// Step 2: configure the completion closure.
let completionClosure = Queries.jsonDataTaskCompletionAdapter(resultType: [Result].self, completionBlock: fetchCompletion)

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
extension Queries {
// Kept internal; the query is 'publicly exposed' through KeymanPackage and ResourceFileManager.
@ -116,7 +117,9 @@ extension Queries {
}
urlComponents.queryItems = queryItems + [URLQueryItem(name: "platform", value: "ios")]
SentryManager.breadcrumbAndLog("Querying package versions through API endpoint: \(urlComponents.url!)")
let message = "Querying package versions through API endpoint: \(urlComponents.url!)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
// Step 2: configure the completion closure.
let completionClosure = Queries.jsonDataTaskCompletionAdapter(resultType: Result.self, completionBlock: fetchCompletion)

View file

@ -9,6 +9,7 @@
import CoreText
import Foundation
import UIKit // for UIFont
import os.log
public class FontManager {
public static let shared = FontManager()
@ -49,13 +50,15 @@ public class FontManager {
private func readFontName(at url: URL) -> String? {
guard let provider = CGDataProvider(url: url as CFURL) else {
log.error("Failed to open \(url)")
let message = "Failed to open \(url)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
return nil
}
guard let font = CGFont(provider),
let name = font.postScriptName
else {
log.error("Failed to read font at \(url)")
let message = "Failed to read font at \(url)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
return nil
}
return name as String
@ -84,13 +87,16 @@ public class FontManager {
didRegister = CTFontManagerRegisterFontsForURL(url as CFURL, .none, &errorRef)
let error = errorRef?.takeRetainedValue() // Releases errorRef
if !didRegister {
log.error("Failed to register font \(fontName) at \(url) reason: \(String(describing: error))")
let message = "Failed to register font \(fontName) at \(url) reason: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
} else {
log.info("Registered font \(fontName) at \(url)")
let message = "Registered font \(fontName) at \(url)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
}
} else {
didRegister = false
log.info("Did not register font at \(url) because font name \(fontName) is already registered")
let message = "Did not register font at \(url) because font name \(fontName) is already registered"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
}
let font = RegisteredFont(name: fontName, isRegistered: didRegister)
fonts[url] = font
@ -111,11 +117,13 @@ public class FontManager {
let didUnregister = CTFontManagerUnregisterFontsForURL(url as CFURL, .none, &errorRef)
let error = errorRef?.takeRetainedValue() // Releases errorRef
if didUnregister {
log.info("Unregistered font \(font.name) at \(url)")
let message = "Unregistered font \(font.name) at \(url)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
font.isRegistered = false
fonts[url] = font
} else {
log.error("Failed to unregister font \(font.name) at \(url) reason: \(String(describing: error))")
let message = "Failed to unregister font \(font.name) at \(url) reason: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
}
}
@ -128,7 +136,8 @@ public class FontManager {
public func registerFonts(in directory: URL) {
guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else {
log.error("Could not list contents of directory \(directory)")
let message = "Could not list contents of directory \(directory)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
return
}
for url in urls where url.lastPathComponent.hasFontExtension {
@ -138,7 +147,8 @@ public class FontManager {
public func unregisterFonts(in directory: URL, fromSystemOnly: Bool = true) {
guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else {
log.error("Could not list contents of directory \(directory)")
let message = "Could not list contents of directory \(directory)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
return
}
for url in urls where url.lastPathComponent.hasFontExtension {

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
/// Mainly differs from the API `Keyboard` by having an associated language.
public struct InstallableKeyboard: Codable, KMPInitializableLanguageResource {
@ -107,7 +108,9 @@ public struct InstallableKeyboard: Codable, KMPInitializableLanguageResource {
let languageMatches = metadata.languages.compactMap { return $0.languageId == lgCode ? $0.name : nil }
if (languageMatches.isEmpty) {
SentryManager.captureAndLog("Could not find languageId '\(lgCode)' for package '\(packageID)'", sentryLevel: .warning)
let message = "Could not find languageId '\(lgCode)' for package '\(packageID)'"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .default, message)
SentryManager.capture(message, sentryLevel: .warning)
}
guard languageMatches.count >= 1 else {
return nil

View file

@ -8,6 +8,7 @@
import Foundation
import Sentry
import os.log
private enum MigrationLevel {
static let initial = 0
@ -63,13 +64,17 @@ public enum Migrations {
migrateUserDefaultsToStructs(storage: storage)
storage.userDefaults.migrationLevel = MigrationLevel.migratedUserDefaultsToStructs
} else {
SentryManager.breadcrumbAndLog("UserDefaults migration to structs already performed. Skipping.", category: "migration")
let message = "UserDefaults migration to structs already performed. Skipping."
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
}
if storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP {
migrateForKMP(storage: storage)
storage.userDefaults.migrationLevel = MigrationLevel.migratedForKMP
} else {
SentryManager.breadcrumbAndLog("KMP directory migration already performed. Skipping.", category: "migration")
let message = "KMP directory migration already performed. Skipping."
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
}
// Version-based migrations
@ -78,10 +83,14 @@ public enum Migrations {
do {
try migrateDocumentsFromPreBrowser()
} catch {
SentryManager.captureAndLog(error, message: "Could not migrate Documents directory contents: \(error)")
let message = "Could not migrate Documents directory contents: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(error, message: message)
}
} else {
SentryManager.breadcrumbAndLog("Documents directory structure compatible with \(Version.fileBrowserImplemented)")
let message = "Documents directory structure compatible with \(Version.fileBrowserImplemented)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
}
if version < Version.packageBasedFileReorg {
@ -89,12 +98,16 @@ public enum Migrations {
try migrateCloudResourcesToKMPFormat()
} catch {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "Could not migrate pre-existing resources to KMP-style file organization")
let message = "Could not migrate pre-existing resources to KMP-style file organization"
event.message = SentryMessage(formatted: message)
event.extra = [ "priorVersion": version ]
SentryManager.captureAndLog(event)
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(event)
}
} else {
SentryManager.breadcrumbAndLog("Resource directories already migrated to package-based format; kmp.jsons already exist.", category: "migration")
let message = "Resource directories already migrated to package-based format; kmp.jsons already exist."
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
}
}
@ -103,7 +116,9 @@ public enum Migrations {
static func detectLegacyKeymanVersion() -> [Version] {
// While the 'key' used to track version info existed before v12, it was unused until then.
SentryManager.breadcrumbAndLog("Prior engine version unknown; attepting to auto-detect.", category: "migration")
let message = "Prior engine version unknown; attepting to auto-detect."
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
// Detect possible version matches.
let userResources = Storage.active.userDefaults.userResources ?? []
@ -259,7 +274,9 @@ public enum Migrations {
do {
try Storage.active.installDefaultKeyboard(from: Resources.bundle)
} catch {
SentryManager.captureAndLog(error, message: "Failed to copy default keyboard from bundle: \(error)")
let message = "Failed to copy default keyboard from bundle: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -272,7 +289,9 @@ public enum Migrations {
do {
try Storage.active.installDefaultLexicalModel(from: Resources.bundle)
} catch {
SentryManager.captureAndLog(error, message: "Failed to copy default lexical model from bundle: \(error)")
let message = "Failed to copy default lexical model from bundle: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -284,13 +303,17 @@ public enum Migrations {
guard let userKeyboardObject = storage.userDefaults.object(forKey: Key.userKeyboardsList),
let currentKeyboardObject = storage.userDefaults.object(forKey: Key.userCurrentKeyboard)
else {
SentryManager.breadcrumbAndLog("User keyboard list or current keyboard missing. Skipping migration.", category: "migration")
let message = "User keyboard list or current keyboard missing. Skipping migration."
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
return
}
guard let oldUserKeyboards = userKeyboardObject as? [[String: String]],
let oldCurrentKeyboard = currentKeyboardObject as? [String: String]
else {
SentryManager.captureAndLog("User keyboard list or current keyboard has an unexpected type")
let message = "User keyboard list or current keyboard has an unexpected type"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.capture(message)
return
}
@ -306,14 +329,18 @@ public enum Migrations {
}
private static func installableKeyboard(from kbDict: [String: String]) -> InstallableKeyboard? {
SentryManager.breadcrumbAndLog("Migrating keyboard dictionary for '\(String(describing: kbDict["kbId"]))", category: "migration", sentryLevel: .debug)
let message = "Migrating keyboard dictionary for '\(String(describing: kbDict["kbId"]))"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .debug, message)
SentryManager.breadcrumb(message)
guard let id = kbDict["kbId"],
let name = kbDict["kbName"],
let languageID = kbDict["langId"],
let languageName = kbDict["langName"],
let version = kbDict["version"]
else {
SentryManager.captureAndLog("Missing required fields in keyboard dictionary: \(kbDict)")
let messageOne = "Missing required fields in keyboard dictionary: \(kbDict)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .default, messageOne)
SentryManager.capture(message)
return nil
}
let rtl = kbDict["rtl"] == "Y"
@ -329,8 +356,11 @@ public enum Migrations {
font: displayFont,
oskFont: oskFont,
isCustom: isCustom)
SentryManager.breadcrumbAndLog("Migrated keyboard dictionary to keyboard \(kb.id)")
log.debug(kb)
let messageTwo = "Migrated keyboard dictionary to keyboard \(kb.id)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, messageTwo)
SentryManager.breadcrumb(message)
let messageThree = "\(kb)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .debug, messageThree)
return kb
}
@ -339,13 +369,17 @@ public enum Migrations {
let languageID = kbDict["langId"]
else {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "Missing required fields in keyboard dictionary for FullKeyboardID")
let message = "Missing required fields in keyboard dictionary for FullKeyboardID"
event.message = SentryMessage(formatted: message)
event.extra = ["kbId": kbDict["kbId"] ?? "nil", "langId": kbDict["langId"] ?? "nil"]
SentryManager.captureAndLog(event)
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(event)
return nil
}
let id = FullKeyboardID(keyboardID: keyboardID, languageID: languageID)
SentryManager.breadcrumbAndLog("Migrated keyboard dictionary to \(id)", category: "migration")
let message = "Migrated keyboard dictionary to \(id)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
return id
}
@ -354,15 +388,18 @@ public enum Migrations {
return nil
}
guard let data = jsonString.data(using: .utf8) else {
log.error("Failed to encode string: \(jsonString)")
let message = "Failed to encode string: \(jsonString)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
return nil
}
guard let fontDict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
log.error("Error parsing String as JSON: \(jsonString)")
return nil
let message = "Error parsing String as JSON: \(jsonString)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
return nil
}
guard let family = fontDict["family"] as? String else {
log.error("Missing 'family' String: \(fontDict)")
let message = "Missing 'family' String: \(fontDict)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
return nil
}
let files: [String]
@ -371,7 +408,8 @@ public enum Migrations {
} else if let filesArray = fontDict["files"] as? [String] {
files = filesArray
} else {
log.error("Missing 'files': \(fontDict)")
let message = "Missing 'files': \(fontDict)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
return nil
}
return Font(family: family, source: files)
@ -382,10 +420,14 @@ public enum Migrations {
let languageDir = storage.baseDir.appendingPathComponent("languages")
let fontDir = storage.baseDir.appendingPathComponent("fonts")
SentryManager.breadcrumbAndLog("Migrating from base directory: \(storage.baseDir)", category: "migration")
let message = "Migrating from base directory: \(storage.baseDir)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
guard var userKeyboards = storage.userDefaults.userKeyboards else {
SentryManager.breadcrumbAndLog("No user keyboards to migrate", category: "migration")
let message = "No user keyboards to migrate"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
return
}
@ -393,7 +435,8 @@ public enum Migrations {
for i in userKeyboards.indices {
let keyboard = userKeyboards[i]
guard let version = latestKeyboardFileVersion(withID: keyboard.id, dirPath: languageDir.path) else {
log.warning("Could not find JS file for keyboard \(keyboard.id) in \(languageDir.path)")
let message = "Could not find JS file for keyboard \(keyboard.id) in \(languageDir.path)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .default, message)
continue
}
var urls = urlsForKeyboard[keyboard.id] ?? Set()
@ -402,12 +445,14 @@ public enum Migrations {
let fontFiles = (keyboard.font?.source ?? []) + (keyboard.oskFont?.source ?? [])
for file in fontFiles {
guard file.hasFontExtension else {
log.info("Skipping copy of \(file) for keyboard \(keyboard.id) since it is not a font file.")
let message = "Skipping copy of \(file) for keyboard \(keyboard.id) since it is not a font file."
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
continue
}
let url = fontDir.appendingPathComponent(file)
guard FileManager.default.fileExists(atPath: url.path) else {
log.warning("Font file \(url) for keyboard \(keyboard.id) does not exist")
let message = "Font file \(url) for keyboard \(keyboard.id) does not exist"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .default, message)
continue
}
urls.insert(url)
@ -426,7 +471,8 @@ public enum Migrations {
withIntermediateDirectories: true,
attributes: nil)
} catch {
log.error("Failed to create keyboard directory at \(keyboardDir)")
let message = "Failed to create keyboard directory at \(keyboardDir)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
continue
}
@ -436,13 +482,15 @@ public enum Migrations {
do {
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch {
log.error("Failed to copy from \(srcURL) to \(dstURL) for keyboard \(keyboardID)")
let message = "Failed to copy from \(srcURL) to \(dstURL) for keyboard \(keyboardID)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
successful = false
}
}
if successful {
successfulKeyboards.append(keyboardID)
log.info("Succesfully copied keyboard files for keyboard \(keyboardID)")
let message = "Succesfully copied keyboard files for keyboard \(keyboardID)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
}
}
@ -476,7 +524,9 @@ public enum Migrations {
}
static func migrateDocumentsFromPreBrowser() throws {
SentryManager.breadcrumbAndLog("Cleaning Documents folder due to 12.0 installation artifacts", category: "migration")
let message = "Cleaning Documents folder due to 12.0 installation artifacts"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .info, message)
SentryManager.breadcrumb(message)
// Actually DO it.
let documentFolderURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
@ -491,16 +541,22 @@ public enum Migrations {
let destFile = fileURL.lastPathComponent.replacingOccurrences(of: ".kmp.zip", with: ".kmp")
let destURL = fileURL.deletingLastPathComponent().appendingPathComponent(destFile)
SentryManager.breadcrumbAndLog("\(fileURL) -> \(destURL)", category: "migration", sentryLevel: .debug)
let message = "\(fileURL) -> \(destURL)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .debug, message)
SentryManager.breadcrumb(message)
try FileManager.default.moveItem(at: fileURL, to: destURL)
} else if fileURL.lastPathComponent == "temp" {
// Removes the 'temp' installation directory; that shouldn't be visible to users.
SentryManager.breadcrumbAndLog("Deleting directory: \(fileURL)", category: "migration", sentryLevel: .debug)
let message = "Deleting directory: \(fileURL)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .debug, message)
SentryManager.breadcrumb(message)
try FileManager.default.removeItem(at: fileURL)
} else if fileURL.lastPathComponent.hasSuffix(".kmp") {
// Do nothing; this file is fine.
} else {
SentryManager.breadcrumbAndLog("Unexpected file found in documents folder during upgrade: \(fileURL)", category: "migration")
let message = "Unexpected file found in documents folder during upgrade: \(fileURL)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .debug, message)
SentryManager.breadcrumb(message)
}
}
}
@ -534,10 +590,12 @@ public enum Migrations {
}
} catch {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "Could not remove version number from filename")
let message = "Could not remove version number from filename"
event.message = SentryMessage(formatted: message)
event.extra = ["package" : resource.packageID ?? "<no package>", "id": resource.id, "location": srcLocation ]
SentryManager.captureAndLog(event)
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(event)
}
}
@ -554,12 +612,16 @@ public enum Migrations {
let tuple = try (ResourceFileManager.shared.prepareKMPInstall(from: filePath), filePath)
return tuple
} catch {
SentryManager.captureAndLog("Could not load kmp.info for local package during migration: \(file)")
let message = "Could not load kmp.info for local package during migration: \(file)"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(message)
return nil
}
}
} catch {
SentryManager.captureAndLog("Could not check contents of Documents directory for resource-migration assist")
let message = "Could not check contents of Documents directory for resource-migration assist"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(message)
}
// Filters out Packages that don't contain the matching resource type.
@ -596,7 +658,9 @@ public enum Migrations {
matched.append(package.findResource(withID: resource.typedFullID)!)
}
} catch {
SentryManager.captureAndLog(error, message: "Could not install resource from locally-cached package: \(String(describing: error))")
let message = "Could not install resource from locally-cached package: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -664,9 +728,11 @@ public enum Migrations {
// write to location.
} catch {
let event = Sentry.Event(level: .error)
event.message = SentryMessage(formatted: "Could not generate kmp.json for legacy resource!")
let message = "Could not generate kmp.json for legacy resource!"
event.message = SentryMessage(formatted: message)
os_log("%{public}s", log: KeymanEngineLogger.engine, type: .error, message)
event.extra = [ "resourceId": package.id, "type": package.resourceType()]
SentryManager.captureAndLog(event)
SentryManager.capture(event)
}
}

View file

@ -7,6 +7,7 @@
//
import Foundation
import os.log
public enum DownloadError : Error {
public enum Cause: Error {
@ -241,26 +242,30 @@ public class ResourceDownloadManager {
Queries.LexicalModel.fetchModels(forLanguageCode: languageID, withSession: session) { results, error in
if let error = error {
// We never quite started downloading the lexical model, so there's no download to have failed.
log.info("Failed to fetch lexical model list for "+languageID+". error: "+error.localizedDescription)
let message = "Failed to fetch lexical model list for \(languageID), error: \(error.localizedDescription)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
try? completionClosure?(nil, error)
return
}
guard let results = results else { // Should not be possible.
//TODO: put up an alert instead
log.info("No lexical models available for language \(languageID) (nil)")
let message = "No lexical models available for language \(languageID) (nil)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
try? completionClosure?(nil, nil)
return
}
if results.count == 0 {
log.info("No lexical models available for language \(languageID) (empty)")
let message = "No lexical models available for language \(languageID) (empty)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
try? completionClosure?(nil, nil)
// We automatically use the first model in the list.
} else {
let lexicalModel = results[0].0
let lmFullID = results[0].0.fullID
log.info("Fetched lexical model list for "+languageID+".")
let message = "Fetched lexical model list for \(languageID)."
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
let closure = completionClosure ?? self.standardLexicalModelInstallCompletionBlock(forFullID: lmFullID)
let downloadURL = self.defaultDownloadURL(forPackage: lexicalModel.packageKey,
@ -496,7 +501,8 @@ public class ResourceDownloadManager {
from: downloadURL) { package, error in
guard let package = package, error == nil else {
let errString = error != nil ? String(describing: error!) : ""
log.error("Could not successfully download package \(key) for update: \(errString)")
let errorMessage = "Could not successfully download package \(key) for update: \(errString)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
return
}
@ -531,11 +537,15 @@ public class ResourceDownloadManager {
// The reason we're deprecating it; only returns the first model, even if more language pairings are installed.
return package.installables[0][0]
} else {
SentryManager.captureAndLog("Specified package (at \(packageURL)) does not contain lexical models: \(KMPError.invalidPackage)")
let message = "Specified package (at \(packageURL)) does not contain lexical models: \(KMPError.invalidPackage)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.capture(message)
return nil
}
} catch {
SentryManager.captureAndLog(error, message: "Error occurred while attempting to install package from \(packageURL): \(String(describing: error))")
let message = "Error occurred while attempting to install package from \(packageURL): \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message: message)
return nil
}
}
@ -560,7 +570,9 @@ public class ResourceDownloadManager {
try handler?(package, error)
self.resourceDownloadCompleted(with: package)
} catch {
SentryManager.captureAndLog(error, message: "Unhandled error occurred after resource successfully downloaded: \(String(describing: error))")
let message = "Unhandled error occurred after resource successfully downloaded: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message: message)
self.resourceDownloadFailed(withKey: packageKey, with: error)
}
@ -615,7 +627,9 @@ public class ResourceDownloadManager {
if let package = package {
do {
try ResourceFileManager.shared.install(resourceWithID: fullID, from: package)
SentryManager.breadcrumbAndLog("successfully parsed the keyboard in: \(package.sourceFolder)")
let message = "successfully parsed the keyboard in: \(package.sourceFolder)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
// Maintains legacy behavior; automatically sets the newly-downloaded keyboard as active.
if let keyboard = package.findResource(withID: fullID) {
@ -626,13 +640,18 @@ public class ResourceDownloadManager {
self.downloadLexicalModelsForLanguageIfExists(languageID: fullID.languageID)
}
} catch {
SentryManager.captureAndLog(error, message: "Keyboard installation error: \(String(describing: error))")
let message = "Keyboard installation error: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message: message)
}
} else if let error = error {
// Often a download error.
log.error("Installation failed: \(String(describing: error))")
let errorMessage = "Installation failed: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
} else {
SentryManager.captureAndLog("Unknown error when attempting to install \(fullID.description))")
let message = "Unknown error when attempting to install \(fullID.description))"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(message)
}
}
}
@ -643,18 +662,25 @@ public class ResourceDownloadManager {
do {
// A raw port of the queue's old installation method for lexical models.
try ResourceFileManager.shared.finalizePackageInstall(package, isCustom: false)
SentryManager.breadcrumbAndLog("successfully parsed the lexical model in: \(package.sourceFolder)")
let message = "successfully parsed the lexical model in: \(package.sourceFolder)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
if let installedLexicalModel = package.findResource(withID: fullID) {
_ = Manager.shared.registerLexicalModel(installedLexicalModel)
}
} catch {
SentryManager.captureAndLog(error, message: "Error installing the lexical model: \(String(describing: error))")
let message = "Error installing the lexical model: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.migration, type: .error, message)
SentryManager.capture(error, message: message)
}
} else if let error = error {
log.error("Error downloading the lexical model \(String(describing: error))")
let errorMessage = "Error downloading the lexical model \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
} else {
SentryManager.captureAndLog("Unknown error when attempting to install \(fullID.description)")
let message = "Unknown error when attempting to install \(fullID.description)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(message)
}
}
}

View file

@ -8,6 +8,7 @@
import Foundation
import Reachability
import os.log
enum DownloadNode {
case simpleBatch(AnyDownloadBatch)
@ -279,7 +280,9 @@ class ResourceDownloadQueue: HTTPDownloadDelegate {
do {
try reachability = Reachability(hostname: KeymanHosts.API_KEYMAN_COM.host!)
} catch {
SentryManager.captureAndLog("Could not start Reachability object: \(error)")
let message = "Could not start Reachability object: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message: message)
}
self.session = session

View file

@ -8,6 +8,7 @@
import Foundation
import UIKit
import os.log
/**
* This class stores common methods used for installing language resources, regardless of source.
@ -40,7 +41,9 @@ public class ResourceFileManager {
do {
try Storage.active.installDefaultKeyboard(from: Resources.bundle)
} catch {
SentryManager.captureAndLog(error, message: "Failed to copy default keyboard from bundle: \(error)")
let message = "Failed to copy default keyboard from bundle: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message:message)
}
}
Migrations.engineVersion = Version.latestFeature
@ -154,7 +157,9 @@ public class ResourceFileManager {
try copyWithOverwrite(from: url, to: destinationUrl)
return destinationUrl
} catch {
SentryManager.captureAndLog(error)
let message = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message:message)
return nil
}
}
@ -166,7 +171,9 @@ public class ResourceFileManager {
*/
public func prepareKMPInstall(from url: URL) throws -> KeymanPackage {
// Once selected, start the standard install process.
SentryManager.breadcrumbAndLog("Opening KMP from \(url)")
let message = "Opening KMP from \(url)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
// Step 1: Copy it to a temporary location, making it a .zip in the process
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
@ -176,7 +183,8 @@ public class ResourceFileManager {
do {
try copyWithOverwrite(from: url, to: archiveUrl)
} catch {
log.error(error)
let errorMessage = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
throw KMPError.copyFiles
}
@ -208,7 +216,8 @@ public class ResourceFileManager {
let kmp = try prepareKMPInstall(from: url)
completionHandler(kmp, nil)
} catch {
log.error(error)
let errorMessage = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
completionHandler(nil, error)
}
}
@ -333,7 +342,8 @@ public class ResourceFileManager {
try finalizePackageInstall(package, isCustom: isCustom)
completionHandler(nil)
} catch {
log.error(error)
let errorMessage = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
completionHandler(error)
}
}
@ -360,7 +370,8 @@ public class ResourceFileManager {
where FullID.Resource.Package: TypedKeymanPackage<FullID.Resource> {
if fullIDs.contains(where: { package.findResource(withID: $0) == nil }) {
let missingResource = fullIDs.first(where: { package.findResource(withID: $0) == nil })!
log.error("Resource with full ID \(missingResource.description) not in package")
let errorMessage = "Resource with full ID \(missingResource.description) not in package"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
throw KMPError.resourceNotInPackage
}
@ -376,7 +387,8 @@ public class ResourceFileManager {
try copyWithOverwrite(from: package.sourceFolder,
to: Storage.active.packageDir(for: package)!)
} catch {
log.error("Could not create installation directory and/or copy resources: \(error)")
let errorMessage = "Could not create installation directory and/or copy resources: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
throw KMPError.fileSystem
}
@ -413,7 +425,9 @@ public class ResourceFileManager {
if !FileManager.default.fileExists(atPath: path) {
// Is 'internal' and only called after packages have been installed,
// thus when the files should already be in-place.
SentryManager.captureAndLog("Could not add resource of type: \(resource.fullID.type) with ID: \(resource.id) because the resource file does not exist")
let message = "Could not add resource of type: \(resource.fullID.type) with ID: \(resource.id) because the resource file does not exist"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(message)
return
}
@ -445,6 +459,8 @@ public class ResourceFileManager {
userDefaults.set([Date()], forKey: Key.synchronizeSWKeyboard)
userDefaults.synchronize()
SentryManager.breadcrumbAndLog("Added \(resource.fullID.type) with ID: \(resource.id) and language code: \(resource.languageID)")
let message = "Added \(resource.fullID.type) with ID: \(resource.id) and language code: \(resource.languageID)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
}
}

View file

@ -8,6 +8,7 @@
import Foundation
import Sentry
import os.log
// MARK: - Static members
extension Storage {
@ -17,7 +18,9 @@ extension Storage {
if paths.isEmpty {
return nil
}
SentryManager.breadcrumbAndLog("Storage.nonShared: using path \(paths[0])")
let message = "Storage.nonShared: using path \(paths[0])"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
return Storage(baseURL: paths[0], userDefaults: UserDefaults.standard)
}()
@ -81,7 +84,9 @@ class Storage {
attributes: nil)
return newDir
} catch {
SentryManager.captureAndLog(error, message: "Failed to create subdirectory at \(newDir)")
let message = ("Failed to create subdirectory at \(newDir)")
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message:message)
return nil
}
}
@ -198,7 +203,9 @@ class Storage {
contents = try FileManager.default.contentsOfDirectory(at: keyboardDir,
includingPropertiesForKeys: [.isDirectoryKey])
} catch {
SentryManager.captureAndLog(error, message: "Failed to list contents at \(keyboardDir) with error \(error)")
let message = ("Failed to list contents at \(keyboardDir) with error \(error)")
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message:message)
return nil
}
return contents.filter { url in
@ -206,7 +213,9 @@ class Storage {
let values = try url.resourceValues(forKeys: [.isDirectoryKey])
return values.isDirectory ?? false
} catch {
SentryManager.captureAndLog(error)
let message = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error)
return false
}
}
@ -263,7 +272,9 @@ extension Storage {
let package = try ResourceFileManager.shared.prepareKMPInstall(from: defaultKMPFile) as! KeyboardKeymanPackage
try ResourceFileManager.shared.install(resourceWithID: Defaults.keyboard.fullID, from: package)
} catch {
SentryManager.captureAndLog(error, message: "Failed to install the default keyboard from the bundled KMP: \(error)")
let message = "Failed to install the default keyboard from the bundled KMP: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message:message)
}
}
@ -287,13 +298,17 @@ extension Storage {
// Install all languages for the model, not just the default-listed one.
try ResourceFileManager.shared.install(resourcesWithIDs: package.installables[0].map { $0.fullID }, from: package)
} catch {
SentryManager.captureAndLog(error, message: "Failed to install the default lexical model from the bundled KMP: \(error)")
let message = "Failed to install the default lexical model from the bundled KMP: \(error)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(error, message:message)
}
}
func copyFiles(to dst: Storage) throws {
if FileManager.default.fileExists(atPath: dst.baseDir.path) {
SentryManager.breadcrumbAndLog("Deleting \(dst.baseDir) for copy from \(baseDir) to \(dst.baseDir)")
let message = "Deleting \(dst.baseDir) for copy from \(baseDir) to \(dst.baseDir)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
try FileManager.default.removeItem(at: dst.baseDir)
}
try FileManager.default.copyItem(at: baseDir, to: dst.baseDir)

View file

@ -9,6 +9,7 @@
import Foundation
import UIKit
import WebKit
import os.log
class ResourceInfoViewController<Resource: LanguageResource>: UIViewController, UIAlertViewDelegate, UITableViewDelegate, UITableViewDataSource {
// The data backing our UI text in the UITableView.
@ -84,7 +85,8 @@ class ResourceInfoViewController<Resource: LanguageResource>: UIViewController,
if let qrImg = generateQRCode(from: resourceURL) {
qrImageView.image = qrImg
} else {
log.info("Unable to generate QR code for URL: \(resourceURL)")
let message = "Unable to generate QR code for URL: \(resourceURL)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
}
} else {
// No resource-sharing link available. Hide the text label!
@ -117,7 +119,7 @@ class ResourceInfoViewController<Resource: LanguageResource>: UIViewController,
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.setToolbarHidden(true, animated: true)
log.info("didAppear: ResourceInfoViewController")
os_log("didAppear: ResourceInfoViewController", log:KeymanEngineLogger.resources, type: .info)
}
func numberOfSections(in tableView: UITableView) -> Int {

View file

@ -8,6 +8,7 @@
//
import QuartzCore
import UIKit
import os.log
private let activityViewTag = -2
private let toolbarButtonTag = 100
@ -88,7 +89,7 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
navigationItem.rightBarButtonItem = addButton
}
log.info("viewDidLoad: InstalledLanguagesViewController (registered for keyboardDownloadStarted)")
os_log("viewDidLoad: InstalledLanguagesViewController (registered for keyboardDownloadStarted)", log:KeymanEngineLogger.resources, type: .info)
}
override public func viewWillAppear(_ animated: Bool) {
@ -104,8 +105,8 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
if numberOfSections(in: tableView) == 0 {
showActivityView()
}
log.info("didAppear: InstalledLanguagesViewController")
os_log("didAppear: InstalledLanguagesViewController", log:KeymanEngineLogger.ui, type: .info)
// Are there updates worth doing?
if isDidUpdateCheck {
// Nope; don't make an 'update' button.
@ -171,11 +172,14 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
for lm in userLexicalModels {
let l = lm.languageID
if let langName = keyboardLanguages[l]?.name {
log.info("keyboard language \(l) \(langName) has lexical model")
let message = "keyboard language \(l) \(langName) has lexical model"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
} else {
// Legacy behavior: we automatically install all MTNT language codes, even without
// a matching keyboard for the more specific variant(s).
SentryManager.breadcrumbAndLog("lexical model language \(l) has no keyboard installed!")
let message = "lexical model language \(l) has no keyboard installed!"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
SentryManager.breadcrumb(message)
}
}
@ -279,7 +283,8 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
}
private func packageDownloadStarted(key: KeymanPackage.Key) {
log.info("download started for \(key.type.rawValue): InstalledLanguagesViewController")
let message = "download started for \(key.type.rawValue): InstalledLanguagesViewController"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
view.isUserInteractionEnabled = false
navigationItem.setHidesBackButton(true, animated: true)
@ -299,7 +304,7 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
}
private func packageDownloadCompleted(package: KeymanPackage) {
log.info("lexicalModelDownloadCompleted: InstalledLanguagesViewController")
os_log("lexicalModelDownloadCompleted: InstalledLanguagesViewController", log:KeymanEngineLogger.resources, type: .info)
DispatchQueue.main.async {
var msg: String
@ -331,7 +336,7 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
return
}
log.info("keyboardDownloadFailed: InstalledLanguagesViewController")
os_log("keyboardDownloadFailed: InstalledLanguagesViewController", log:KeymanEngineLogger.resources, type: .info)
DispatchQueue.main.async {
var msg: String
@ -353,7 +358,7 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
}
private func batchUpdateStarted(_: [AnyLanguageResource]) {
log.info("batchUpdateStarted: InstalledLanguagesViewController")
os_log("batchUpdateStarted: InstalledLanguagesViewController", log:KeymanEngineLogger.resources, type: .info)
view.isUserInteractionEnabled = false
navigationItem.setHidesBackButton(true, animated: true)
@ -447,7 +452,8 @@ extension InstalledLanguagesViewController {
case .error(let error):
// Note: Errors may result from network issues.
if let error = error {
log.error(String(describing: error))
let errorMessage = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
}
case .success(let package, let fullID):
ResourceFileManager.shared.doInstallPrompt(for: package as! KeyboardKeymanPackage,

View file

@ -8,6 +8,7 @@
import UIKit
import Sentry
import os.log
private let toolbarButtonTag = 100
@ -38,7 +39,8 @@ class LanguageSettingsViewController: UITableViewController {
super.viewDidLoad()
let titleFormat = NSLocalizedString("menu-langsettings-title", bundle: engineBundle, comment: "")
title = String.localizedStringWithFormat(titleFormat, language.name)
log.info("viewDidLoad: LanguageSettingsViewController title: \(title ?? "<empty>")")
let message = "viewDidLoad: LanguageSettingsViewController title: \(title ?? "<empty>")"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, message)
if Manager.shared.canAddNewKeyboards {
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self,
@ -50,7 +52,7 @@ class LanguageSettingsViewController: UITableViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
log.info("didAppear: LanguageSettingsViewController")
os_log("viewDidAppear: LanguageSettingsViewController", log:KeymanEngineLogger.ui, type: .info)
}
@ -309,7 +311,8 @@ class LanguageSettingsViewController: UITableViewController {
case .error(let error):
if let error = error {
// Note: Errors may result from network issues.
log.error(String(describing: error))
let errorMessage = "\(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .error, errorMessage)
}
case .success(let package, let fullID):
ResourceFileManager.shared.doInstallPrompt(for: package as! KeyboardKeymanPackage,
@ -346,8 +349,10 @@ class LanguageSettingsViewController: UITableViewController {
// If user defaults for keyboards list does not exist, do nothing.
guard let globalUserKeyboards = userData.userKeyboards else {
SentryManager.captureAndLog("no keyboards in the global keyboards list!")
return nil
let message = "no keyboards in the global keyboards list!"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, message)
SentryManager.capture(message)
return nil
}
if let index = globalUserKeyboards.firstIndex(where: { $0.fullID == matchingFullID }) {
@ -361,7 +366,8 @@ class LanguageSettingsViewController: UITableViewController {
event.extra = ["id": matchingFullID]
SentrySDK.capture(event: event)
log.error("this keyboard \(matchingFullID) not found among user's installed keyboards!")
let errorMessage = "this keyboard \(matchingFullID) not found among user's installed keyboards!"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
return nil
}
}
@ -373,7 +379,9 @@ class LanguageSettingsViewController: UITableViewController {
// If user defaults for keyboards list does not exist, do nothing.
guard let globalUserKeyboards = userData.userKeyboards else {
SentryManager.captureAndLog("no keyboards in the global keyboards list!")
let message = "No keyboards in the global keyboards list!"
os_log("%{public}s", log: KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture("no keyboards in the global keyboards list!")
return
}
@ -392,7 +400,8 @@ class LanguageSettingsViewController: UITableViewController {
event.extra = ["id": matchingFullID]
SentrySDK.capture(event: event)
log.error("this keyboard \(matchingFullID) not found among user's installed keyboards!")
let errorMessage = "this keyboard \(matchingFullID) not found among user's installed keyboards!"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .error, errorMessage)
return
}
}

View file

@ -7,6 +7,7 @@
//
import UIKit
import os.log
open class SettingsViewController: UITableViewController {
private var itemsArray = [[String: String]]()
@ -16,7 +17,7 @@ open class SettingsViewController: UITableViewController {
super.viewWillAppear(animated)
loadUserLanguages()
log.info("willAppear: SettingsViewController")
os_log("viewWillAppear: SettingsViewController", log:KeymanEngineLogger.settings, type: .info)
}
override open func viewDidLoad() {
@ -40,7 +41,7 @@ open class SettingsViewController: UITableViewController {
let sb : UIStoryboard = UIStoryboard(name: "Settings", bundle: nil)
if let vc = sb.instantiateInitialViewController() {
launchingVC.present(vc, animated: true, completion: {
log.info("presented settings")
os_log("presented settings", log:KeymanEngineLogger.settings, type: .info)
})
}
}
@ -199,7 +200,9 @@ open class SettingsViewController: UITableViewController {
case "systemkeyboardsettings", "installfile", "forcederror", "spacebartext":
break
default:
SentryManager.captureAndLog("unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")")
let message = "unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
cell.accessoryType = .none
}
@ -272,7 +275,9 @@ open class SettingsViewController: UITableViewController {
case "showbanner", "showgetstarted":
cell.detailTextLabel?.isEnabled = false
default:
SentryManager.captureAndLog("unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")")
let message = "unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
}
}
@ -296,7 +301,9 @@ open class SettingsViewController: UITableViewController {
case "systemkeyboardsettings":
guard let appSettings = URL(string: UIApplication.openSettingsURLString) else {
// It is an error if the option is displayed but unusable. That's bad UI.
SentryManager.captureAndLog("Could not launch keyboard settings menu")
let message = "Could not launch keyboard settings menu"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
return
}
UniversalLinks.externalLinkLauncher?(appSettings)
@ -304,7 +311,9 @@ open class SettingsViewController: UITableViewController {
if let block = Manager.shared.fileBrowserLauncher {
block(navigationController!)
} else {
SentryManager.captureAndLog("Listener for framework signal to launch file browser is missing")
let message = "Listener for framework signal to launch file browser is missing"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
}
case "forcederror":
SentryManager.forceError()
@ -357,9 +366,9 @@ open class SettingsViewController: UITableViewController {
// In a storyboard-based application, you will often want to do a little preparation before navigation
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
log.info("prepare for segue")
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
os_log("prepare for segue", log:KeymanEngineLogger.settings, type: .info)
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
// MARK: - language access -
@ -405,11 +414,14 @@ open class SettingsViewController: UITableViewController {
for lm in userLexicalModels {
let l = lm.languageID
if let langName = keyboardLanguages[l]?.name {
log.info("keyboard language \(l) \(langName) has lexical model")
let message = "keyboard language \(l) \(langName) has lexical model"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
} else {
// Legacy behavior: we automatically install all MTNT language codes, even without
// a matching keyboard for the more specific variant(s).
SentryManager.breadcrumbAndLog("lexical model language \(l) has no keyboard installed!")
let message = "lexical model language \(l) has no keyboard installed!"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .info, message)
SentryManager.breadcrumb(message)
}
}
@ -438,7 +450,9 @@ open class SettingsViewController: UITableViewController {
nc.pushViewController(vc, animated: true)
setIsDoneButtonEnabled(nc, true)
} else {
SentryManager.captureAndLog("no navigation controller for showing languages???")
let message = ("No navigation controller for showing languages???")
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
}
}
@ -448,7 +462,9 @@ open class SettingsViewController: UITableViewController {
nc.pushViewController(vc, animated: true)
setIsDoneButtonEnabled(nc, true)
} else {
SentryManager.captureAndLog("no navigation controller for showing spacebarText options")
let message = ("No navigation controller for showing spacebarText options")
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
SentryManager.capture(message)
}
}

View file

@ -8,6 +8,7 @@
import AudioToolbox
import UIKit
import os.log
public class TextField: UITextField, KeymanResponder {
// viewController should be set to main view controller to enable keyboard picker.
@ -83,7 +84,7 @@ public class TextField: UITextField, KeymanResponder {
}
if delegate !== delegateProxy {
log.error("Trying to set TextField's delegate directly. Use setKeymanDelegate() instead.")
os_log("Trying to set TextField's delegate directly. Use setKeymanDelegate() instead.", log:KeymanEngineLogger.ui, type: .error)
}
super.delegate = delegateProxy
}
@ -95,7 +96,8 @@ public class TextField: UITextField, KeymanResponder {
// All of the normal UITextFieldDelegate methods are supported.
public func setKeymanDelegate(_ keymanDelegate: TextFieldDelegate?) {
delegateProxy.keymanDelegate = keymanDelegate
log.debug("TextField: \(self.hashValue) keymanDelegate set to: \(keymanDelegate.debugDescription)")
let message = "TextField: \(self.hashValue) keymanDelegate set to: \(keymanDelegate.debugDescription)"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message)
}
public override var text: String! {
@ -146,7 +148,8 @@ public class TextField: UITextField, KeymanResponder {
font = UIFont.systemFont(ofSize: fontSize)
}
log.debug("TextField \(self.hashValue) setFont: \(font?.familyName ?? "nil")")
let message = "TextField \(self.hashValue) setFont: \(font?.familyName ?? "nil")"
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .debug, message)
}
@objc func enableInputClickSound() {
@ -171,7 +174,8 @@ extension KeymanResponder where Self: TextField {
// Dismisses the keyboard if this textview is the first responder.
// - Use this instead of [resignFirstResponder] as it also resigns the Keyman keyboard's responders.
public func dismissKeyboard() {
log.debug("TextField: \(self.hashValue) dismissing keyboard. Was first responder: \(isFirstResponder)")
let message = "TextField: \(self.hashValue) dismissing keyboard. Was first responder: \(isFirstResponder)"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message)
resignFirstResponder()
Manager.shared.inputViewController.endEditing(true)
}
@ -258,10 +262,12 @@ extension TextField: UITextFieldDelegate {
font = UIFont.systemFont(ofSize: fontSize)
}
log.debug("TextField: \(self.hashValue) setFont: \(font?.familyName ?? "nil")")
let messageOne = "TextField: \(self.hashValue) setFont: \(font?.familyName ?? "nil")"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, messageOne)
log.debug("TextField: \(self.hashValue) Became first responder. Value: \(String(describing: text))")
}
let messageTwo = "TextField: \(self.hashValue) Became first responder. Value: \(String(describing: text))"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, messageTwo)
}
public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField == self {

View file

@ -8,6 +8,7 @@
import AudioToolbox
import UIKit
import os.log
public class TextView: UITextView, KeymanResponder {
// viewController should be set to main view controller to enable keyboard picker.
@ -80,8 +81,8 @@ public class TextView: UITextView, KeymanResponder {
}
if delegate !== delegateProxy {
log.error("Trying to set TextView's delegate directly. Use setKeymanDelegate() instead.")
}
os_log("Trying to set TextView's delegate directly. Use setKeymanDelegate() instead.", log:KeymanEngineLogger.ui, type: .error)
}
super.delegate = delegateProxy
}
}
@ -92,7 +93,8 @@ public class TextView: UITextView, KeymanResponder {
// - All of the normal UITextViewDelegate methods are supported.
public func setKeymanDelegate(_ keymanDelegate: TextViewDelegate?) {
delegateProxy.keymanDelegate = keymanDelegate
log.debug("TextView: \(self.hashValue) keymanDelegate set to: \(keymanDelegate.debugDescription)")
let message = "TextView: \(self.hashValue) keymanDelegate set to: \(keymanDelegate.debugDescription)"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message)
}
public override var text: String! {
@ -133,7 +135,8 @@ public class TextView: UITextView, KeymanResponder {
font = UIFont.systemFont(ofSize: fontSize)
}
log.debug("TextView: \(self.hashValue) setFont: \(font?.familyName ?? "nil")")
let message = "TextView: \(self.hashValue) setFont: \(font?.familyName ?? "nil")"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message)
}
// MARK: iOS 7 TextView Scroll bug fix
@ -168,7 +171,8 @@ extension KeymanResponder where Self: TextView {
// Dismisses the keyboard if this textview is the first responder.
// - Use this instead of [resignFirstResponder] as it also resigns the Keyman keyboard's responders.
public func dismissKeyboard() {
log.debug("TextView: \(self.hashValue) dismissing keyboard. Was first responder: \(isFirstResponder)")
let message = "TextView: \(self.hashValue) dismissing keyboard. Was first responder: \(isFirstResponder)"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, message)
resignFirstResponder()
Manager.shared.inputViewController.endEditing(true)
}
@ -251,9 +255,11 @@ extension TextView: UITextViewDelegate {
font = UIFont.systemFont(ofSize: fontSize)
}
log.debug("TextView: \(self.hashValue) setFont: \(font?.familyName ?? "nil")")
let messageOne = "TextView: \(self.hashValue) setFont: \(font?.familyName ?? "nil")"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, messageOne)
log.debug("TextView: \(self.hashValue) Became first responder. Value: \(String(describing: text))")
let messageTwo = "TextView: \(self.hashValue) Became first responder. Value: \(String(describing: text))"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .debug, messageTwo)
}
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange,

View file

@ -9,6 +9,7 @@
import XCTest
@testable import KeymanEngine
import DeviceKit
import os.log
extension KeyboardSize: Equatable {
public static func == (lhs: KeyboardSize, rhs: KeyboardSize) -> Bool {
@ -51,7 +52,8 @@ class KeyboardScaleTests: XCTestCase {
func testScaleForUnknown() {
let device = Device.unknown("granny smith")
log.info(device.isPad)
let message = "\(device.isPad)"
os_log("%{public}s", log:KeymanEngineLogger.ui, type: .info, message)
let size1 = CGSize(width: 375, height: 667) // iPhone 8 device height
let mappedScale1 = KeyboardScaleMap.getDeviceDefaultKeyboardScale(forPortrait: true,

View file

@ -127,13 +127,11 @@ class KeymanPackageTests: XCTestCase {
do {
if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) {
log.info("*** first unzip of \(kmp.id)")
do {
// clear directory before second extract
try KeymanPackage.clearDirectory(destination: destinationFolderURL)
if let secondKmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) {
log.info("*** second unzip of \(secondKmp.id)")
} else {
XCTAssert(false, "*** second unzip failed")
}

View file

@ -7,6 +7,7 @@
//
import XCTest
import os.log
@testable import KeymanEngine
class ResourceUpdateTests: XCTestCase {
@ -60,7 +61,7 @@ class ResourceUpdateTests: XCTestCase {
// Uses the XCTest 'attachment' system to retrieve the desired file.
self.add(try TestUtils.EngineStateBundler.createBundle(withName: "khmer_angkor update base"))
log.info("Bundle archived and attached to test's report.")
os_log("Bundle archived and attached to test's report.", log:KeymanEngineLogger.resources, type: .info)
}
func testCacheCurrent() {

View file

@ -7,7 +7,6 @@
//
import Foundation
import XCGLogger
import XCTest
extension TestUtils.Downloading {

View file

@ -9,6 +9,7 @@
import Foundation
import XCTest
import ZIPFoundation
import os.log
@testable import KeymanEngine
@ -41,13 +42,16 @@ extension TestUtils {
try FileManager.default.createDirectory(at: bundleConstructionURL.appendingPathComponent("Library"), withIntermediateDirectories: true, attributes: nil)
log.info("Documents directory source: \(documentsDirectory)")
let messageOne = "Documents directory source: \(documentsDirectory)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, messageOne)
try FileManager.default.copyItem(at: documentsDirectory, to: bundleConstructionURL.appendingPathComponent("Documents"))
log.info("Library source: \(storageDirectory)")
let messageTwo = "Library source: \(storageDirectory)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, messageTwo)
try FileManager.default.copyItem(at: storageDirectory, to: bundleConstructionURL.appendingPathComponent("Library").appendingPathComponent("keyman"))
log.info("Preferences directory source: \(preferencesDirectory)")
let messageThree = "Preferences directory source: \(preferencesDirectory)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, messageThree)
let pListPath = bundleConstructionURL.appendingPathComponent("Library").appendingPathComponent("Preferences")
let testEngineFilename = "com.keyman.testing.KeymanEngineTestHost.plist"
@ -68,7 +72,8 @@ extension TestUtils {
let archiveURL = bundleConstructionURL.appendingPathComponent("bundleArchive.zip")
do {
_ = try Archive(url: archiveURL, accessMode: .create)
log.info("archiveURL: \(archiveURL)")
let message = "archiveURL: \(archiveURL)"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .info, message)
let attachment = XCTAttachment(contentsOfFile: archiveURL)
attachment.lifetime = .keepAlways
return attachment

View file

@ -7,8 +7,9 @@
//
import XCTest
import Foundation
import os.log
@testable import KeymanEngine
/**
@ -40,8 +41,9 @@ enum TestUtils {
try FileManager.default.removeItem(at: item)
}
} catch {
log.error(error)
XCTFail("Could not clear a directory because of error: \(error)")
let message = "Could not clear a directory because of error: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .debug, message)
XCTFail("Could not clear a directory because of error: \(error)")
}
}
@ -57,7 +59,8 @@ enum TestUtils {
withIntermediateDirectories: true,
attributes: nil)
} catch {
log.error(error)
let message = "Could not properly erase & reinit KeymanEngine Storage because of error: \(String(describing: error))"
os_log("%{public}s", log:KeymanEngineLogger.resources, type: .debug, message)
XCTFail("Could not properly erase & reinit KeymanEngine Storage because of error: \(error)")
}
}

View file

@ -16,6 +16,8 @@
165EB39C2097181D00040A69 /* KMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB39B2097181D00040A69 /* KMView.swift */; };
296EF2C42AFA267500E3E384 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */; };
296EF2C52AFA267500E3E384 /* ZIPFoundation.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
29B30C212B56325D00C342A4 /* KeymanLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CA9A612B4F9B0B00EAB099 /* KeymanLogger.swift */; };
29CA9A622B4F9B0B00EAB099 /* KeymanLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CA9A612B4F9B0B00EAB099 /* KeymanLogger.swift */; };
981AFA8F19EF44DE006706BF /* 724-info@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9815725518E4F0930014DF0C /* 724-info@2x.png */; };
981AFA9619EF44DE006706BF /* MainViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98ABADCE176935E500B62590 /* MainViewController_iPhone.xib */; };
981AFA9819EF44DE006706BF /* 724-info.png in Resources */ = {isa = PBXBuildFile; fileRef = 9815725418E4F0930014DF0C /* 724-info.png */; };
@ -116,7 +118,6 @@
C055F0B11F60E8D400140735 /* GetStartedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C055F0B01F60E8D400140735 /* GetStartedViewController.swift */; };
C055F0B31F60F7A400140735 /* DropDownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C055F0B21F60F7A400140735 /* DropDownView.swift */; };
C055F0B51F610FB200140735 /* DropDownListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C055F0B41F610FB200140735 /* DropDownListView.swift */; };
C059FCC01FD927EF00BD1A64 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = C059FCBF1FD927EF00BD1A64 /* Log.swift */; };
C0A1A6311F5E4F4300EAF822 /* InfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1A6301F5E4F4300EAF822 /* InfoViewController.swift */; };
C0A1A6331F5E749000EAF822 /* SetUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1A6321F5E749000EAF822 /* SetUpViewController.swift */; };
C0A1A6351F5E786A00EAF822 /* BookmarksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A1A6341F5E786A00EAF822 /* BookmarksViewController.swift */; };
@ -149,14 +150,10 @@
CEBD34322654FE3500EB2EA8 /* KeymanEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE80AD32257F2B4A008D2150 /* KeymanEngine.framework */; };
CEBD343B2654FEB400EB2EA8 /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34352654FEB400EB2EA8 /* DeviceKit.xcframework */; };
CEBD343C2654FEB400EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34352654FEB400EB2EA8 /* DeviceKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD343D2654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34362654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework */; };
CEBD343E2654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34362654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD343F2654FEB400EB2EA8 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34372654FEB400EB2EA8 /* Reachability.xcframework */; };
CEBD34402654FEB400EB2EA8 /* Reachability.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34372654FEB400EB2EA8 /* Reachability.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD34412654FEB400EB2EA8 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */; };
CEBD34422654FEB400EB2EA8 /* Sentry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD34432654FEB400EB2EA8 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */; };
CEBD34442654FEB400EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEF4E55623E95B7B0065B9C7 /* ImageBanner.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEF4E55523E95B7B0065B9C7 /* ImageBanner.xib */; };
CEF4E55723E95B7B0065B9C7 /* ImageBanner.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEF4E55523E95B7B0065B9C7 /* ImageBanner.xib */; };
CEF4E55923E967140065B9C7 /* ImageBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF4E55823E967140065B9C7 /* ImageBannerViewController.swift */; };
@ -182,11 +179,9 @@
dstSubfolderSpec = 10;
files = (
CEBD34402654FEB400EB2EA8 /* Reachability.xcframework in Embed Frameworks */,
CEBD343E2654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */,
296EF2C52AFA267500E3E384 /* ZIPFoundation.xcframework in Embed Frameworks */,
CEBD343C2654FEB400EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */,
CEBD34422654FEB400EB2EA8 /* Sentry.xcframework in Embed Frameworks */,
CEBD34442654FEB400EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */,
CE80AD34257F2B4B008D2150 /* KeymanEngine.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
@ -215,6 +210,7 @@
29BFA75D282931F8009FCCC3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
29BFA769282946EC009FCCC3 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
29C1E17028001EC200759EDE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
29CA9A612B4F9B0B00EAB099 /* KeymanLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanLogger.swift; sourceTree = "<group>"; };
37378F4222C9AA610043F22B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainViewController_iPad.xib; sourceTree = "<group>"; };
37378F4322C9AA630043F22B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainViewController_iPhone.xib; sourceTree = "<group>"; };
9815725018E4F0930014DF0C /* 702-share.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "702-share.png"; path = "Keyman Icons/702-share.png"; sourceTree = "<group>"; };
@ -319,7 +315,6 @@
C055F0B01F60E8D400140735 /* GetStartedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = GetStartedViewController.swift; sourceTree = "<group>"; tabWidth = 2; };
C055F0B21F60F7A400140735 /* DropDownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DropDownView.swift; sourceTree = "<group>"; tabWidth = 2; };
C055F0B41F610FB200140735 /* DropDownListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DropDownListView.swift; sourceTree = "<group>"; tabWidth = 2; };
C059FCBF1FD927EF00BD1A64 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
C0A1A6301F5E4F4300EAF822 /* InfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = InfoViewController.swift; sourceTree = "<group>"; tabWidth = 2; };
C0A1A6321F5E749000EAF822 /* SetUpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = SetUpViewController.swift; sourceTree = "<group>"; tabWidth = 2; };
C0A1A6341F5E786A00EAF822 /* BookmarksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = BookmarksViewController.swift; sourceTree = "<group>"; tabWidth = 2; };
@ -341,10 +336,8 @@
CEACC90F25F07D5A006EAB45 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/Localizable.strings; sourceTree = "<group>"; };
CEACC91225F07D77006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
CEBD34352654FEB400EB2EA8 /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = "<absolute>"; };
CEBD34362654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = "<absolute>"; };
CEBD34372654FEB400EB2EA8 /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = "<absolute>"; };
CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = "<absolute>"; };
CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = "<absolute>"; };
CEBD3458265511B700EB2EA8 /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/Localizable.strings; sourceTree = "<group>"; };
CEEC468226F2F7BA009A5B7D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
CEEF81B92673019600EE6A07 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
@ -358,11 +351,9 @@
buildActionMask = 2147483647;
files = (
CEBD343F2654FEB400EB2EA8 /* Reachability.xcframework in Frameworks */,
CEBD343D2654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */,
296EF2C42AFA267500E3E384 /* ZIPFoundation.xcframework in Frameworks */,
CEBD343B2654FEB400EB2EA8 /* DeviceKit.xcframework in Frameworks */,
CEBD34412654FEB400EB2EA8 /* Sentry.xcframework in Frameworks */,
CEBD34432654FEB400EB2EA8 /* XCGLogger.xcframework in Frameworks */,
CE80AD33257F2B4A008D2150 /* KeymanEngine.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -546,10 +537,8 @@
children = (
296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */,
CEBD34352654FEB400EB2EA8 /* DeviceKit.xcframework */,
CEBD34362654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework */,
CEBD34372654FEB400EB2EA8 /* Reachability.xcframework */,
CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */,
CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */,
CE80AD32257F2B4A008D2150 /* KeymanEngine.framework */,
);
name = Frameworks;
@ -568,8 +557,8 @@
16A9229D20325253003CC98E /* Info.plist */,
162E2C9420926C8600F40769 /* Classes */,
C0E943F91F6124E100E7D98C /* AppDelegate.swift */,
C059FCBF1FD927EF00BD1A64 /* Log.swift */,
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */,
29CA9A612B4F9B0B00EAB099 /* KeymanLogger.swift */,
);
path = Keyman;
sourceTree = "<group>";
@ -1042,9 +1031,9 @@
buildActionMask = 2147483647;
files = (
165EB39C2097181D00040A69 /* KMView.swift in Sources */,
29CA9A622B4F9B0B00EAB099 /* KeymanLogger.swift in Sources */,
C055F0B11F60E8D400140735 /* GetStartedViewController.swift in Sources */,
C055F0B51F610FB200140735 /* DropDownListView.swift in Sources */,
C059FCC01FD927EF00BD1A64 /* Log.swift in Sources */,
CE7FF1F0239A0293007859D9 /* PackageBrowserViewController.swift in Sources */,
C0E943F81F61234C00E7D98C /* UIImage+Helpers.swift in Sources */,
C0E943FE1F61377900E7D98C /* MainViewController.swift in Sources */,
@ -1070,6 +1059,7 @@
buildActionMask = 2147483647;
files = (
CEF4E55A23E967140065B9C7 /* ImageBannerViewController.swift in Sources */,
29B30C212B56325D00C342A4 /* KeymanLogger.swift in Sources */,
C0FF769B1F5D368C00BD23C3 /* KeyboardViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View file

@ -10,6 +10,7 @@ import KeymanEngine
import UIKit
import WebKit
import Sentry
import os.log
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
@ -49,7 +50,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
vc.present(nvc, animated: true, completion: nil)
}
} else {
SentryManager.captureAndLog("Cannot find app's root UIViewController")
let message = "Cannot find app's root UIViewController"
os_log("%{public}s", log: KeymanLogger.ui, type: .info, message)
SentryManager.capture(message)
}
return true
@ -60,8 +63,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
SentryManager.start()
// Forces the logs to initialize, as their definitions result in lazy init.
// These references have been configured to also log app details.
_ = log
_ = KeymanEngine.log
// In iOS 15, navigation bars become transparent by default when the edge
// of the scrollable content aligns with the edge of the navigation bar.
@ -139,7 +140,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
ResourceFileManager.shared.promptPackageInstall(of: package, in: nvc, isCustom: true)
vc.present(nvc, animated: true, completion: nil)
} else {
SentryManager.captureAndLog("Cannot find app's root UIViewController")
let message = "Cannot find app's root UIViewController"
os_log("%{public}s", log: KeymanLogger.ui, type: .error, message)
SentryManager.capture(message)
}
}
return true

View file

@ -9,6 +9,7 @@
import KeymanEngine
import WebKit
import Reachability
import os
class InfoViewController: UIViewController, WKNavigationDelegate {
@IBOutlet var webView: WKWebView!
@ -41,7 +42,9 @@ class InfoViewController: UIViewController, WKNavigationDelegate {
try networkReachable = Reachability(hostname: "keyman.com")
try networkReachable?.startNotifier()
} catch {
SentryManager.captureAndLog(error, message: "error starting Reachability notifier: \(error)")
let message = "error starting Reachability notifier: \(error)"
os_log("%{public}s", log:KeymanLogger.ui, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -67,7 +70,7 @@ class InfoViewController: UIViewController, WKNavigationDelegate {
func webView(_ webView: WKWebView, didFailNavigation error: Error) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
updateButtons()
log.debug(error)
os_log("%{public}s", log: KeymanLogger.ui, type: .error, error.localizedDescription)
}
@IBAction func back(_ sender: Any) {
@ -100,6 +103,6 @@ class InfoViewController: UIViewController, WKNavigationDelegate {
let appVersion = Version.current.majorMinor
let url = "\(KeymanHosts.HELP_KEYMAN_COM)/products/iphone-and-ipad/\(appVersion.plainString)/"
webView.load(URLRequest(url: URL(string: url)!))
log.debug("Info page URL: \(url)")
os_log("Info page URL: %{public}s", log: KeymanLogger.ui, type: .debug, url)
}
}

View file

@ -9,6 +9,7 @@
import KeymanEngine
import UIKit
import QuartzCore
import os
// Internal strings
private let baseUri = "https://r.keymanweb.com/20/fonts/get_mobileconfig.php?id="
@ -191,7 +192,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
userData.set(self.textView?.text, forKey: userTextKey)
userData.set(self.textSize.description, forKey: userTextSizeKey)
userData.synchronize()
log.debug("saving text size: \(textSize.description).")
os_log("saving text size: %{public}s", log: KeymanLogger.settings, type: .debug, textSize.description)
}
private func calculateDefaultTextSize() -> CGFloat {

View file

@ -9,6 +9,7 @@
import KeymanEngine
import WebKit
import Reachability
import os
// TODO: Refactor common functionality from InfoViewController
class SetUpViewController: UIViewController, WKNavigationDelegate {
@ -35,7 +36,9 @@ class SetUpViewController: UIViewController, WKNavigationDelegate {
try networkReachable = Reachability(hostname: "keyman.com")
try networkReachable?.startNotifier()
} catch {
SentryManager.captureAndLog(error, message: "error thrown starting Reachability notifier: \(error)")
let message = "error thrown starting Reachability notifier: \(error)"
os_log("%{public}s", log:KeymanLogger.ui, type: .error, message)
SentryManager.capture(error, message: message)
}
}
@ -74,6 +77,6 @@ class SetUpViewController: UIViewController, WKNavigationDelegate {
let url = "\(KeymanHosts.HELP_KEYMAN_COM)/products/iphone-and-ipad/\(appVersion.plainString)"
+ "/start/installing-system-keyboard?embed=ios"
webView.load(URLRequest(url: URL(string: url)!))
log.debug("Set up page URL: \(url)")
os_log("Set up page URL: %{public}s", log: KeymanLogger.resources, type: .debug, url)
}
}

View file

@ -0,0 +1,33 @@
/**
* Keyman is copyright (C) SIL International. MIT License.
*
* KeymanLogger.swift
* Keyman
*
* Created by Shawn Schantz on 2024-01-11.
*
* Basic logging capability provided by wrapping Apple's Unified Logging
* Framework. Works with iOS 10 and later.
* Log statements are written with a call the `os_log` API which takes a
* message, an `OSLog` reference and log level.
* Several static references to `OSLog` objects are defined, each of which
* determines the subsystem and category of subsequent log statements.
* Both subsystem and category are useful for filtering with the Console app.
* Use the same subsystem name for all `OSLog` objects created here, but specify
* a unique category to identify related functionality.
* Add as many static `OSLog` resources as necessary to support new log
* categories.
*/
import Foundation
import os.log
// bundle for engine is "Tavultesoft.Keyman"
// but selecting this subsystem name for consistency across apps
private let subsystem = "com.keyman.ios.app"
public struct KeymanLogger {
static let ui = OSLog(subsystem: subsystem, category: "UI")
static let resources = OSLog(subsystem: subsystem, category: "Resources")
static let settings = OSLog(subsystem: subsystem, category: "Settings")
}

View file

@ -1,34 +0,0 @@
//
// Log.swift
// Keyman
//
// Created by Gabriel Wong on 2017-12-07.
// Copyright © 2017 SIL International. All rights reserved.
//
import XCGLogger
// From XCGLogger docs:
// Note: This creates the log object lazily, which means it's not created until it's actually needed.
public let log: XCGLogger = {
// Default: the 'console', which is read by Xcode but doesn't reach the system logs.
let mainLog = XCGLogger(identifier: "Keyman", includeDefaultDestinations: false)
// Ensures our log messages go out to the device's system log as well as the console.
let systemLogDest = AppleSystemLogDestination(identifier: "")
systemLogDest.showLogIdentifier = true
mainLog.add(destination: systemLogDest)
// Temporary logging level to ensure that app details are reported properly.
mainLog.outputLevel = .info
mainLog.logAppDetails()
#if DEBUG
mainLog.outputLevel = .debug
#else
mainLog.outputLevel = .warning
#endif
return mainLog
}()

View file

@ -9,6 +9,7 @@
import Foundation
import UIKit
import KeymanEngine // for log commands
import os
/**
* Takes in a XIB spec for an image banner and makes it renderable, applying size constraints when rendered
@ -49,7 +50,8 @@ class ImageBannerViewController: UIViewController {
let frame = CGRect(origin: self.view.frame.origin, size: size)
log.debug("Rendering banner image of size \(size)")
os_log("Rendering banner image of size %{public}s", log: KeymanLogger.ui, type: .debug, NSCoder.string(for: size))
self.view.frame = frame
widthConstraint?.constant = size.width
heightConstraint?.constant = size.height

View file

@ -9,6 +9,7 @@
import KeymanEngine
import UIKit
import Sentry
import os.log
class KeyboardViewController: InputViewController {
var topBarImageSource: ImageBannerViewController!
@ -21,8 +22,6 @@ class KeyboardViewController: InputViewController {
// is enabled. They seem to get blocked otherwise, except in the Simulator.
SentryManager.start(sendingEnabled: true)
}
_ = log
_ = KeymanEngine.log
Manager.applicationGroupIdentifier = "group.KM4I"
@ -70,7 +69,9 @@ class KeyboardViewController: InputViewController {
let imgPath = getTopBarImage(size: size)
guard let path = imgPath else {
SentryManager.captureAndLog("No image specified for the image banner!")
let message = "No image specified for the image banner!"
os_log("%{public}s", log: KeymanLogger.ui, type: .error, message)
SentryManager.capture(message)
return
}

View file

@ -18,14 +18,10 @@
CEBD337E26549EC100EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD337C26549EC100EB2EA8 /* KeymanEngine.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33C62654CB0E00EB2EA8 /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33B92654CA4B00EB2EA8 /* DeviceKit.xcframework */; };
CEBD33C72654CB0F00EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33B92654CA4B00EB2EA8 /* DeviceKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33C82654CB1000EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BC2654CA5900EB2EA8 /* ObjcExceptionBridging.xcframework */; };
CEBD33C92654CB1100EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BC2654CA5900EB2EA8 /* ObjcExceptionBridging.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33CA2654CB1300EB2EA8 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BF2654CA5900EB2EA8 /* Reachability.xcframework */; };
CEBD33CB2654CB1300EB2EA8 /* Reachability.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BF2654CA5900EB2EA8 /* Reachability.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33CC2654CB1400EB2EA8 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */; };
CEBD33CD2654CB1500EB2EA8 /* Sentry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33CE2654CB1600EB2EA8 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */; };
CEBD33CF2654CB1700EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CED2AC85260AFADC00A408D2 /* ekwtamil99uni.kmp in Resources */ = {isa = PBXBuildFile; fileRef = CED2AC84260AFADC00A408D2 /* ekwtamil99uni.kmp */; };
/* End PBXBuildFile section */
@ -37,11 +33,9 @@
dstSubfolderSpec = 10;
files = (
CEBD33CB2654CB1300EB2EA8 /* Reachability.xcframework in Embed Frameworks */,
CEBD33C92654CB1100EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */,
290FB45C2AFB234900249D58 /* ZIPFoundation.xcframework in Embed Frameworks */,
CEBD33C72654CB0F00EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */,
CEBD33CD2654CB1500EB2EA8 /* Sentry.xcframework in Embed Frameworks */,
CEBD33CF2654CB1700EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */,
CEBD337E26549EC100EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
@ -60,8 +54,6 @@
C0324B831F87172900AF3785 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
CEBD337C26549EC100EB2EA8 /* KeymanEngine.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = KeymanEngine.xcframework; sourceTree = "<group>"; };
CEBD33B92654CA4B00EB2EA8 /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = "<group>"; };
CEBD33BC2654CA5900EB2EA8 /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = "<group>"; };
CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = "<group>"; };
CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = "<group>"; };
CEBD33BF2654CA5900EB2EA8 /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = "<group>"; };
CED2AC84260AFADC00A408D2 /* ekwtamil99uni.kmp */ = {isa = PBXFileReference; lastKnownFileType = file; path = ekwtamil99uni.kmp; sourceTree = "<group>"; };
@ -72,13 +64,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CEBD33C82654CB1000EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */,
CEBD33CA2654CB1300EB2EA8 /* Reachability.xcframework in Frameworks */,
290FB45B2AFB234900249D58 /* ZIPFoundation.xcframework in Frameworks */,
CEBD33CC2654CB1400EB2EA8 /* Sentry.xcframework in Frameworks */,
CEBD33C62654CB0E00EB2EA8 /* DeviceKit.xcframework in Frameworks */,
CEBD337D26549EC100EB2EA8 /* KeymanEngine.xcframework in Frameworks */,
CEBD33CE2654CB1600EB2EA8 /* XCGLogger.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -128,10 +118,8 @@
isa = PBXGroup;
children = (
290FB45A2AFB234800249D58 /* ZIPFoundation.xcframework */,
CEBD33BC2654CA5900EB2EA8 /* ObjcExceptionBridging.xcframework */,
CEBD33BF2654CA5900EB2EA8 /* Reachability.xcframework */,
CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */,
CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */,
CEBD33B92654CA4B00EB2EA8 /* DeviceKit.xcframework */,
CEBD337C26549EC100EB2EA8 /* KeymanEngine.xcframework */,
);

View file

@ -15,8 +15,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
KeymanEngine.log.outputLevel = .debug
KeymanEngine.log.logAppDetails()
return true
}
}

View file

@ -23,22 +23,16 @@
CEBD33D92654CB7800EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33E22654CB8700EB2EA8 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */; };
CEBD33E32654CB8700EB2EA8 /* Reachability.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33E42654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */; };
CEBD33E52654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33E62654CB8800EB2EA8 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */; };
CEBD33E72654CB8800EB2EA8 /* Sentry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD33E82654CB8800EB2EA8 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */; };
CEBD33E92654CB8800EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBEFB94231FB4E000F8F67C /* banner-portrait-text.png in Resources */ = {isa = PBXBuildFile; fileRef = CEBEFB92231FB4DF00F8F67C /* banner-portrait-text.png */; };
CEBEFB95231FB4E000F8F67C /* banner-landscape-text.png in Resources */ = {isa = PBXBuildFile; fileRef = CEBEFB93231FB4DF00F8F67C /* banner-landscape-text.png */; };
CED2AC8D260B07CF00A408D2 /* ekwtamil99uni.kmp in Resources */ = {isa = PBXBuildFile; fileRef = CED2AC8C260B07CF00A408D2 /* ekwtamil99uni.kmp */; };
CEDB3276265C953A000A2009 /* KeymanEngine.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */; };
CEDB3277265C953A000A2009 /* KeymanEngine.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
CEDB3279265C9B15000A2009 /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */; };
CEDB327A265C9B33000A2009 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */; };
CEDB327B265C9B33000A2009 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */; };
CEDB327C265C9B33000A2009 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */; };
CEDB327D265C9B33000A2009 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -70,12 +64,10 @@
dstSubfolderSpec = 10;
files = (
CEBD33E32654CB8700EB2EA8 /* Reachability.xcframework in Embed Frameworks */,
CEBD33E52654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */,
290FB45F2AFB270D00249D58 /* ZIPFoundation.xcframework in Embed Frameworks */,
CEBD33E72654CB8800EB2EA8 /* Sentry.xcframework in Embed Frameworks */,
CEBD33D92654CB7800EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */,
CEBD33D42654CB6A00EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */,
CEBD33E92654CB8800EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -110,9 +102,7 @@
CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = KeymanEngine.xcframework; sourceTree = "<group>"; };
CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = "<group>"; };
CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = "<group>"; };
CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = "<group>"; };
CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = "<group>"; };
CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = "<group>"; };
CEBEFB92231FB4DF00F8F67C /* banner-portrait-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "banner-portrait-text.png"; sourceTree = "<group>"; };
CEBEFB93231FB4DF00F8F67C /* banner-landscape-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "banner-landscape-text.png"; sourceTree = "<group>"; };
CED2AC8C260B07CF00A408D2 /* ekwtamil99uni.kmp */ = {isa = PBXFileReference; lastKnownFileType = file; path = ekwtamil99uni.kmp; sourceTree = "<group>"; };
@ -124,11 +114,9 @@
buildActionMask = 2147483647;
files = (
CEBD33E22654CB8700EB2EA8 /* Reachability.xcframework in Frameworks */,
CEBD33E42654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */,
290FB45E2AFB270D00249D58 /* ZIPFoundation.xcframework in Frameworks */,
CEBD33E62654CB8800EB2EA8 /* Sentry.xcframework in Frameworks */,
CEBD33D82654CB7800EB2EA8 /* DeviceKit.xcframework in Frameworks */,
CEBD33E82654CB8800EB2EA8 /* XCGLogger.xcframework in Frameworks */,
CEBD33D32654CB6A00EB2EA8 /* KeymanEngine.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -137,11 +125,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CEDB327A265C9B33000A2009 /* ObjcExceptionBridging.xcframework in Frameworks */,
CEDB327B265C9B33000A2009 /* Reachability.xcframework in Frameworks */,
290FB4602AFB271C00249D58 /* ZIPFoundation.xcframework in Frameworks */,
CEDB327C265C9B33000A2009 /* Sentry.xcframework in Frameworks */,
CEDB327D265C9B33000A2009 /* XCGLogger.xcframework in Frameworks */,
CEDB3279265C9B15000A2009 /* DeviceKit.xcframework in Frameworks */,
CEDB3276265C953A000A2009 /* KeymanEngine.xcframework in Frameworks */,
);
@ -196,10 +182,8 @@
isa = PBXGroup;
children = (
290FB45D2AFB270D00249D58 /* ZIPFoundation.xcframework */,
CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */,
CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */,
CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */,
CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */,
CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */,
CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */,
);

View file

@ -15,10 +15,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
// Set desired level before release
KeymanEngine.log.outputLevel = .debug
KeymanEngine.log.logAppDetails()
// Replace with your application group id
// Ensure this happens before installing any keyboards or models within the engine
// whenever using App Group Identifiers.

View file

@ -11,10 +11,6 @@ import UIKit
class KeyboardViewController: InputViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
// Set desired level before release
KeymanEngine.log.outputLevel = .debug
KeymanEngine.log.logAppDetails()
// Replace with your application group id
Manager.applicationGroupIdentifier = "group.KMSample"
@ -56,7 +52,7 @@ class KeyboardViewController: InputViewController {
func setupTopBarImage(isPortrait: Bool) {
let imgPath = getTopBarImage(isPortrait: isPortrait)
guard let path = imgPath else {
log.error("No image specified for the image banner!")
print("No image specified for the image banner!")
return
}

View file

@ -1,5 +1,4 @@
github "weichsel/ZIPFoundation" ~> 0.9
github "keymanapp/dependency-XCGLogger" "master"
github "devicekit/DeviceKit" ~> 5.0
github "ashleymills/Reachability.swift"
github "getsentry/sentry-cocoa" ~> 8.7.0

View file

@ -1,5 +1,4 @@
github "ashleymills/Reachability.swift" "v5.1.0"
github "devicekit/DeviceKit" "5.1.0"
github "getsentry/sentry-cocoa" "8.15.2"
github "keymanapp/dependency-XCGLogger" "57a7b975dbb6fe4fe90cef3d1bc52b8adbd89113"
github "weichsel/ZIPFoundation" "0.9.17"

View file

@ -17,6 +17,8 @@
2990208E278FF402004F18CF /* KeyboardTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2990208D278FF402004F18CF /* KeyboardTableController.swift */; };
2990209127901DD7004F18CF /* KeyboardDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2990209027901DD7004F18CF /* KeyboardDetailCell.swift */; };
2993F6E62791621D009E1352 /* KeyboardDetailController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2993F6E52791621D009E1352 /* KeyboardDetailController.swift */; };
29B30C252B5A8A4800C342A4 /* FVLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B30C242B5A8A4800C342A4 /* FVLogger.swift */; };
29B30C262B5A8A4800C342A4 /* FVLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B30C242B5A8A4800C342A4 /* FVLogger.swift */; };
37C183B922932AD2009F9EFD /* FVKeyboardList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C183B822932AD2009F9EFD /* FVKeyboardList.swift */; };
37C183BD2293321C009F9EFD /* Instructions in Resources */ = {isa = PBXBuildFile; fileRef = 37C183BC2293321C009F9EFD /* Instructions */; };
37EA1F23228A1F57003E710C /* FVInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EA1F22228A1F57003E710C /* FVInputViewController.swift */; };
@ -52,20 +54,14 @@
CEBD34032654D41200EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34012654D41200EB2EA8 /* KeymanEngine.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD341A2654D76600EB2EA8 /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34052654D4EB00EB2EA8 /* DeviceKit.xcframework */; };
CEBD341B2654D76600EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34052654D4EB00EB2EA8 /* DeviceKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD341D2654D76800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34082654D5AA00EB2EA8 /* ObjcExceptionBridging.xcframework */; };
CEBD341E2654D76800EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34082654D5AA00EB2EA8 /* ObjcExceptionBridging.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD341F2654D76900EB2EA8 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD340C2654D5AB00EB2EA8 /* Reachability.xcframework */; };
CEBD34202654D76900EB2EA8 /* Reachability.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD340C2654D5AB00EB2EA8 /* Reachability.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD34222654D76B00EB2EA8 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34092654D5AA00EB2EA8 /* Sentry.xcframework */; };
CEBD34232654D76B00EB2EA8 /* Sentry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34092654D5AA00EB2EA8 /* Sentry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD34252654D76C00EB2EA8 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD340A2654D5AA00EB2EA8 /* XCGLogger.xcframework */; };
CEBD34262654D76C00EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD340A2654D5AA00EB2EA8 /* XCGLogger.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEBD34472654FF3300EB2EA8 /* KeymanEngine.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34012654D41200EB2EA8 /* KeymanEngine.xcframework */; };
CEDB327F265C9C58000A2009 /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34052654D4EB00EB2EA8 /* DeviceKit.xcframework */; };
CEDB3280265C9C58000A2009 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34082654D5AA00EB2EA8 /* ObjcExceptionBridging.xcframework */; };
CEDB3281265C9C58000A2009 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD340C2654D5AB00EB2EA8 /* Reachability.xcframework */; };
CEDB3282265C9C58000A2009 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34092654D5AA00EB2EA8 /* Sentry.xcframework */; };
CEDB3283265C9C58000A2009 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD340A2654D5AA00EB2EA8 /* XCGLogger.xcframework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -76,11 +72,9 @@
dstSubfolderSpec = 10;
files = (
CEBD34202654D76900EB2EA8 /* Reachability.xcframework in Embed Frameworks */,
CEBD341E2654D76800EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */,
290FB4642AFB358F00249D58 /* ZIPFoundation.xcframework in Embed Frameworks */,
CEBD341B2654D76600EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */,
CEBD34232654D76B00EB2EA8 /* Sentry.xcframework in Embed Frameworks */,
CEBD34262654D76C00EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */,
CEBD34032654D41200EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
@ -108,6 +102,7 @@
2990208D278FF402004F18CF /* KeyboardTableController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardTableController.swift; sourceTree = "<group>"; };
2990209027901DD7004F18CF /* KeyboardDetailCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardDetailCell.swift; sourceTree = "<group>"; };
2993F6E52791621D009E1352 /* KeyboardDetailController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardDetailController.swift; sourceTree = "<group>"; };
29B30C242B5A8A4800C342A4 /* FVLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FVLogger.swift; sourceTree = "<group>"; };
37C183B822932AD2009F9EFD /* FVKeyboardList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FVKeyboardList.swift; sourceTree = "<group>"; };
37C183BC2293321C009F9EFD /* Instructions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Instructions; path = FirstVoices/Instructions; sourceTree = "<group>"; };
37EA1F22228A1F57003E710C /* FVInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FVInputViewController.swift; sourceTree = "<group>"; };
@ -145,9 +140,7 @@
98C9A9FA1BFC1111009E9A4F /* SWKeyboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = SWKeyboard.entitlements; sourceTree = "<group>"; };
CEBD34012654D41200EB2EA8 /* KeymanEngine.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = KeymanEngine.xcframework; sourceTree = "<group>"; };
CEBD34052654D4EB00EB2EA8 /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = Carthage/Build/DeviceKit.xcframework; sourceTree = "<group>"; };
CEBD34082654D5AA00EB2EA8 /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = "<group>"; };
CEBD34092654D5AA00EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = Carthage/Build/Sentry.xcframework; sourceTree = "<group>"; };
CEBD340A2654D5AA00EB2EA8 /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = Carthage/Build/XCGLogger.xcframework; sourceTree = "<group>"; };
CEBD340C2654D5AB00EB2EA8 /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = Carthage/Build/Reachability.xcframework; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -156,13 +149,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CEBD341D2654D76800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */,
CEBD341F2654D76900EB2EA8 /* Reachability.xcframework in Frameworks */,
290FB4632AFB358F00249D58 /* ZIPFoundation.xcframework in Frameworks */,
CEBD34222654D76B00EB2EA8 /* Sentry.xcframework in Frameworks */,
CEBD341A2654D76600EB2EA8 /* DeviceKit.xcframework in Frameworks */,
CEBD34022654D41200EB2EA8 /* KeymanEngine.xcframework in Frameworks */,
CEBD34252654D76C00EB2EA8 /* XCGLogger.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -171,11 +162,9 @@
buildActionMask = 2147483647;
files = (
CEDB327F265C9C58000A2009 /* DeviceKit.xcframework in Frameworks */,
CEDB3280265C9C58000A2009 /* ObjcExceptionBridging.xcframework in Frameworks */,
290FB4652AFB359A00249D58 /* ZIPFoundation.xcframework in Frameworks */,
CEDB3281265C9C58000A2009 /* Reachability.xcframework in Frameworks */,
CEDB3282265C9C58000A2009 /* Sentry.xcframework in Frameworks */,
CEDB3283265C9C58000A2009 /* XCGLogger.xcframework in Frameworks */,
CEBD34472654FF3300EB2EA8 /* KeymanEngine.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -240,6 +229,7 @@
9889E8C71BFA9CEC00019560 /* LaunchScreen.storyboard */,
9889E8C21BFA9CEC00019560 /* Main.storyboard */,
9889E8B91BFA9CEC00019560 /* Supporting Files */,
29B30C242B5A8A4800C342A4 /* FVLogger.swift */,
);
path = FirstVoices;
sourceTree = "<group>";
@ -292,10 +282,8 @@
isa = PBXGroup;
children = (
290FB4622AFB358E00249D58 /* ZIPFoundation.xcframework */,
CEBD34082654D5AA00EB2EA8 /* ObjcExceptionBridging.xcframework */,
CEBD340C2654D5AB00EB2EA8 /* Reachability.xcframework */,
CEBD34092654D5AA00EB2EA8 /* Sentry.xcframework */,
CEBD340A2654D5AA00EB2EA8 /* XCGLogger.xcframework */,
CEBD34052654D4EB00EB2EA8 /* DeviceKit.xcframework */,
CEBD34012654D41200EB2EA8 /* KeymanEngine.xcframework */,
);
@ -501,6 +489,7 @@
2990209127901DD7004F18CF /* KeyboardDetailCell.swift in Sources */,
37EA1F4622921BAD003E710C /* KeyboardInfo.swift in Sources */,
296E22E527ED8FA4007BDA12 /* DownloadStatusToolbar.swift in Sources */,
29B30C252B5A8A4800C342A4 /* FVLogger.swift in Sources */,
294D40E9279FE62600DB37F6 /* KeyboardRepository.swift in Sources */,
2990208E278FF402004F18CF /* KeyboardTableController.swift in Sources */,
37EA1F31228B7661003E710C /* FVShared.swift in Sources */,
@ -513,6 +502,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
29B30C262B5A8A4800C342A4 /* FVLogger.swift in Sources */,
37EA1F32228BCDA7003E710C /* FVShared.swift in Sources */,
37EA1F23228A1F57003E710C /* FVInputViewController.swift in Sources */,
);

View file

@ -22,13 +22,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
FVRegionStorage.upgrade()
#if DEBUG
KeymanEngine.log.outputLevel = .debug
KeymanEngine.log.logAppDetails()
#else
KeymanEngine.log.outputLevel = .warning
#endif
// Replace with your application group id
Manager.applicationGroupIdentifier = FVConstants.groupID
Manager.shared.spacebarText = .KEYBOARD

View file

@ -0,0 +1,31 @@
/**
* Keyman is copyright (C) SIL International. MIT License.
*
* FVLogger.swift
* FirstVoices
*
* Created by Shawn Schantz on 2024-01-19.
*
* Basic logging capability provided by wrapping Apple's Unified Logging
* Framework. Works with iOS 10 and later.
* Log statements are written with a call the `os_log` API which takes a
* message, an `OSLog` reference and log level.
* Several static references to `OSLog` objects are defined, each of which
* determines the subsystem and category of subsequent log statements.
* Both subsystem and category are useful for filtering with the Console app.
* Use the same subsystem name for all `OSLog` objects created here, but specify
* a unique category to identify related functionality.
* Add as many static `OSLog` resources as necessary to support new log
* categories.
*/
import Foundation
import os.log
private let subsystem = "com.firstvoices.keyboards"
public struct FVLogger {
static let ui = OSLog(subsystem: subsystem, category: "UI")
static let resources = OSLog(subsystem: subsystem, category: "Resources")
static let settings = OSLog(subsystem: subsystem, category: "Settings")
}

View file

@ -11,16 +11,11 @@
import KeymanEngine
import UIKit
import os.log
@objc(FVInputViewController)
class FVInputViewController: InputViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
#if DEBUG
KeymanEngine.log.outputLevel = .debug
KeymanEngine.log.logAppDetails()
#else
KeymanEngine.log.outputLevel = .warning
#endif
Manager.applicationGroupIdentifier = FVConstants.groupID
@ -77,7 +72,7 @@ class FVInputViewController: InputViewController {
func setupTopBarImage(isPortrait: Bool) {
let imgPath = getTopBarImage(isPortrait: isPortrait)
guard let path = imgPath else {
log.error("No image specified for the image banner!")
os_log("No image specified for the image banner!", log: FVLogger.ui, type: .error)
return
}