iOS Swift: Send log information to iOS app from iOS framework

Lohith Korupolu

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
        }

    }
}
Scriptable

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.

edited at
0

Comments

0 comments
Login to comment

Related