I am trying to find out how to send logging information from my iOS framework to my iOS app. Inside my iOS app, I have the Crashlytics setup, and would like to use the Crashlytics.sharedInstance().recordError(error)
whenever I get some logs from the app as well as my iOS framework.
I read through multiple answers mentioning Crashlytics can be initialized only once in the App and it is not recommended to initialize again in the framework. (for example Link 1), Link 2, Link 3)
Does anyone have a working solution for such a requirement?
EDIT 1 (adding more code from my framework where I need to get the errors from)
public final class MyAppSession {
public fileprivate(set) var isValid: Bool = false
let itemKey = MyAppSession.MyAppKeychainAccount
let keychainAccessGroupName = MyAppConfigured?.keyChainGroup
/**
Attempts to restore a locally stored session.
*/
public static func savedSession() -> MyAppSession? {
do {
let itemKey = MyAppSession.MyAppKeychainAccount
let keychainAccessGroupName = MyAppConfigured?.keyChainGroup
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecAttrAccessGroup as String: keychainAccessGroupName as AnyObject
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad == noErr {
if let result = result as? Data {
if let arrayFromData = NSKeyedUnarchiver.unarchiveObject(with: result) as? NSDictionary {
// Found successfully
return MyAppSession(accessToken: (arrayFromData["accessToken"] as? String)!, refreshToken: (arrayFromData["refreshToken"] as? String)!)
}
}
} else {
MyApp.log("No Keychain elements present.", logLevel: .verbose)
MyAppLogger.sendLogs(message: "Logger Test. No Keychain elements present.")
}
let keychainData = try Locksmith.loadDataForUserAccount(userAccount: MyAppSession.MyAppKeychainAccount)
if let accessToken = keychainData?[MyAppSession.accessTokenKeychainKey] as? String,
let refreshToken = keychainData?[MyAppSession.refreshTokenKeychainKey] as? String {
MyApp.log("Restored saved session.", logLevel: .verbose)
return MyAppSession(accessToken: accessToken, refreshToken: refreshToken)
}
else {
return nil
}
}
catch {
return nil
}
}
init?(accessToken: String, refreshToken: String) {
// Save credentials to the keychain.
do {
try Locksmith.updateData(
data: [MyAppSession.accessTokenKeychainKey: accessToken as AnyObject, MyAppSession.refreshTokenKeychainKey: refreshToken as AnyObject],
forUserAccount: MyAppSession.MyAppKeychainAccount)
if let configuration = MyAppConfigured {
if configuration.keyChainGroup == "" {
let valueData = [MyAppSession.accessTokenKeychainKey: accessToken as AnyObject, MyAppSession.refreshTokenKeychainKey: refreshToken as AnyObject]
let data = NSKeyedArchiver.archivedData(withRootObject: valueData)
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: data as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
let resultCode = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != noErr {
MyApp.log("Keychain groupname empty: \(resultCode)", logLevel: .verbose)
}
else {
MyApp.log("KeyChain Saving Success", logLevel: .verbose)
}
}
else {
let valueData = [MyAppSession.accessTokenKeychainKey: accessToken as AnyObject, MyAppSession.refreshTokenKeychainKey: refreshToken as AnyObject]
let data = NSKeyedArchiver.archivedData(withRootObject: valueData)
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: data as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecAttrAccessGroup as String: keychainAccessGroupName as AnyObject
]
let resultCode = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != noErr {
MyApp.log("Error saving to Keychain: \(resultCode)", logLevel: .verbose)
}
else {
MyApp.log("KeyChain Saving Success", logLevel: .verbose)
}
}
}
isValid = true
MyApp.log("Created new session.")
}
catch {
return nil
}
}
}
One option would be to delegate the logging of the error to your application. This makes it much more flexible.
For example:
protocol MyFrameworkErrorHandlingDelegate: AnyObject {
func didReceiveError(_ error: Error)
}
Then in your application you can create a default extension of this
extension MyFrameworkErrorHandlingDelegate {
func didReceiveError(_ error: Error) {
Crashlytics.sharedInstance().recordError(error)
}
}
The problem with this approach is deciding where to use it or where to assign the delegate and this depends on what your framework does and how your application uses the framework.
EDIT:
Disclaimer: This won't be the most efficient way to do this, but it would be the most straight forward.
protocol MyFrameworkErrorHandlingDelegate: AnyObject {
func didReceiveError(_ error: Error)
}
class MyAppLogger {
static var delegate: MyFrameworkErrorHandlingDelegate?
static func log(_ error: Error) {
delegate?.didReceiveError(error)
}
}
class AppDelegate: UIApplicationDelegate, MyFrameworkErrorHandlingDelegate
later in your AppDelegate
MyAppLogger.delegate = self
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments