aws-amplify/amplify-swift

AWSCloudWatchLoggingCategoryClient asks for Current User Identifier During Configuration (CRASH)

Hartistic opened this issue · 16 comments

Describe the bug

While configuring Amplify with an AWSCloudWatchLoggingPluginConfiguration and an AWSCognitoAuthPlugin configuration the library has a race condition that crashes the application 50% of the time with an error of Thread 20: Fatal error: Authentication category is not configured. Call Amplify.configure() before using any methods on the category.

The problem is that these two configurations are added to Amplify and this crash occurs when calling Amplify.configure().

Order of Methods that causes crash to occur:

  1. try configure(CategoryType.logging.category, using: resolvedConfiguration)
  2. try configurable.configure(using: configuration)
  3. try configure(using: categoryConfiguration(from: amplifyConfiguration))
  4. try Amplify.configure(plugins: Array(plugins.values), using: configuration)
  5. try plugin.configure(using: pluginConfiguration)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
            self.loggingClient.takeUserIdentifierFromCurrentUser()
        }
func takeUserIdentifierFromCurrentUser() {
        Task {
            do {
                let user = try await authentication.getCurrentUser()
                self.userIdentifier = user.userId
            } catch {
                self.userIdentifier = nil
            }
            self.updateSessionControllers()
        }
    }
public func getCurrentUser() async throws -> AuthUser {
        try await plugin.getCurrentUser()
    }
return Fatal.preconditionFailure(
                """
                \(categoryType.displayName) category is not configured. Call Amplify.configure() before using \
                any methods on the category.
                """
            )

Steps To Reproduce

Steps to reproduce the behavior:
1. Add AWSCloudWatchLoggingPluginConfiguration and AWSCognitoAuthPlugin to Amplify:

    private func addAuthenticationPlugin() throws {
        let authPlugin = AWSCognitoAuthPlugin(networkPreferences: Self.networkPreferences)
        do {
            try Amplify.add(plugin: authPlugin)
        } catch {
            throw error
        }
    }

    private func addCloudWatchLoggingPlugin() throws {
        do {
            let logLevels: [String: LogLevel] = ["AWSAuthFetchSessionTask": .error]
            let loggingConstraints = LoggingConstraints(defaultLogLevel: .debug, categoryLogLevel: logLevels)
            let loggingConfiguration = AWSCloudWatchLoggingPluginConfiguration(logGroupName: "your log group",
                                                                               region: "us-east-1",
                                                                               localStoreMaxSizeInMB: 1,
                                                                               flushIntervalInSeconds: 10,
                                                                               loggingConstraints: loggingConstraints)
            let plugin = AWSCloudWatchLoggingPlugin(loggingPluginConfiguration: loggingConfiguration)
            try Amplify.add(plugin: plugin)
        } catch {
            throw error
        }
    }
2. load a amplifyconfiguration.json and call configure:

    private func loadAmplifyConfiguration() throws {
        do {
            let amplifyConfig = try getLocalAmplifyConfigurationFile()
            try Amplify.configure(amplifyConfig)
        } catch {
            throw error
        }
    }
3. On iPhone 15 Pro Max and newer seems to crash way more often.

Expected behavior

The AWSCloudWatchLoggingPluginConfiguration shouldn't be asking for Authentication details during it's configuration...the library says you can only call configure() once, so configuring Auth and then Logging isn't possible.

Amplify Framework Version

2.41.1

Amplify Categories

Analytics, API, Auth

Dependency manager

Swift PM

Swift version

5.8.0

CLI version

I am not sure

Xcode version

16.0

Relevant log output

No response

Is this a regression?

Yes

Regression additional context

No response

Platforms

iOS

OS Version

IOS 18

Device

iPhone 15 Pro Max

Specific to simulators

no

Additional context

No response

Hello,

AWSCloudWatchLoggingPlugin needs Auth Plugin to be configured. You'd need to setup Amplify Auth through CLI by following steps here: https://docs.amplify.aws/gen1/swift/build-a-backend/auth/set-up-auth/

Then you can setup AWSCloudWatchLoggingPlugin by following steps here: https://docs.amplify.aws/gen1/swift/build-a-backend/more-features/logging/set-up-logging/

import Amplify
import AWSCognitoAuthPlugin
import AWSCloudWatchLoggingPlugin

private func addCloudWatchLoggingPlugin() throws {
    do {
        let logLevels: [String: LogLevel] = ["AWSAuthFetchSessionTask": .error]
        let loggingConstraints = LoggingConstraints(defaultLogLevel: .debug, categoryLogLevel: logLevels)
        let loggingConfiguration = AWSCloudWatchLoggingPluginConfiguration(logGroupName: "your log group",
                                                                           region: "us-east-1",
                                                                           localStoreMaxSizeInMB: 1,
                                                                           flushIntervalInSeconds: 10,
                                                                           loggingConstraints: loggingConstraints)
        let plugin = AWSCloudWatchLoggingPlugin(loggingPluginConfiguration: loggingConfiguration)
        try Amplify.add(plugin: plugin)
    } catch {
        throw error
    }
}

init() {
    do {
        try Amplify.add(plugin: AWSCognitoAuthPlugin())
        try addCloudWatchLoggingPlugin()
        try Amplify.configure()
    } catch {
        assert(false, "Error initializing Amplify: \(error)")
    }
}

Thank you so much for the quick response @thisisabhash, we are not using the CLI and Auth is already setup. I think the main culprit seems to be this:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
        self.loggingClient.takeUserIdentifierFromCurrentUser()
}

@Hartistic Can you share where addAuthenticationPlugin and addCloudWatchLoggingPlugin is being used in your project?

Absolutely @harsh62, and thank you guys so much, Im actually hoping I am just doing something out of order and that this is an easy resolution.

We have a SwiftUI Application.

1). On App Launch:

  • We start listening to the Auth hub first (this way we know when Amplify Configuration finishes):
 self.unsubscribeToken = Amplify.Hub.listen(to: .auth) { payload in
      UserStateMachine.shared.handleEvent(payload)
}
  • Next we add all the plugins we need to Amplify:
       // Authentication:
       try addAuthenticationPlugin()
       // Notifications:
       try addNotificationPlugin()
       // Cloud Logging:
       try addCloudWatchLoggingPlugin()
       // Pinpoint Analytics:
       try addAnalyticsPlugin()
       // AWS API:
       try addAPIPlugin()
  • Next we call Amplify.configure (we load our amplifyconfiguration.json file locally that has our specific settings)
   private func loadAmplifyConfiguration() throws {
        do {
            let amplifyConfig = try self.loadConfiguration() // Loads JSON file
            try Amplify.configure(amplifyConfig)
        } catch {
            throw error
        }
    }
  • Once try await for configuring is done we immediately call a force refresh:
    /// Check The Auth Session:
    func forceRefreshAuthSession() async throws {
        let plugin = try getAWSCognitoAuthPlugin()
        let session = try await plugin.fetchAuthSession(options: .forceRefresh())
        print("::: AUTH PLUGIN: AMPLIFY USER SESSION IS SIGNED IN - \(session.isSignedIn)")
    }

Got it and why do you suspect that the following snippet is a problem?

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
     self.loggingClient.takeUserIdentifierFromCurrentUser()
}

From what I see is that the Auth plugin is being configured before the logging plugin, so this snippet should work, because Auth is already configured.

Are you able to share symbolicated crash log too?

@harsh62 absolutely, I cannot share it on this thread, could I share it with you privately somehow?

Got it and why do you suspect that the following snippet is a problem?

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
     self.loggingClient.takeUserIdentifierFromCurrentUser()
}

From what I see is that the Auth plugin is being configured before the logging plugin, so this snippet should work, because Auth is already configured.

@harsh62 my crash backtraces lead back to that code block.

@harsh62 I work with @Hartistic ... IIRC the issue has to do with CloudWatch asking Auth for the user id and it's doing it in a way that when Auth gets an error because we don't have a UserPool it crashes. We're using Identity Pools only for federated logins using Sign in with Apple (no User Pools)

@harsh62 my Slack is: joshua.hart@dfinitiv.io if that helps

Crash Message : Thread 14: Fatal error: Authentication category is not configured. Call Amplify.configure() before using any methods on the category.

@Hartistic @martzcodes I have tried a couple of times setting up a new project with the plugins you described and I am still not able to reproduce the issue. Is it possible you can create and share a demo app where the issue is reproducible?

@harsh62 my Slack is: joshua.hart@dfinitiv.io if that helps

Crash Message : Thread 14: Fatal error: Authentication category is not configured. Call Amplify.configure() before using any methods on the category.

Can you reach out to me on Discord #harsh62 and you can also join our Discord server https://discord.gg/f75sSP34.

@harsh62 it definitely seems like a race condition. Again way more prevalent on the newer devices (try on the iPhone 15 Pro Max or 16 Pro Max). If we remove the AWSCloudWatchLoggingPluginConfiguration plugin, no crashes occur and everything runs smoothly. I will try and make a demo project that contains the crash when I get some available time. I do think from our perspective, this code should have some fail safes in it:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
     self.loggingClient.takeUserIdentifierFromCurrentUser()
}

It does seem to get first before Auth is configured.

@harsh62 it definitely seems like a race condition. Again way more prevalent on the newer devices (try on the iPhone 15 Pro Max or 16 Pro Max). If we remove the AWSCloudWatchLoggingPluginConfiguration plugin, no crashes occur and everything runs smoothly. I will try and make a demo project that contains the crash when I get some available time. I do think from our perspective, this code should have some fail safes in it:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
     self.loggingClient.takeUserIdentifierFromCurrentUser()
}

It does seem to get first before Auth is configured.

Perhaps this is ran after the Auth Configuration is confirmed configured in the library?

@Hartistic Would you be able to share your apps verbose logs, so that I can compare it with mine.. ou can enable verbose logging to the console by doing the following before calling Amplify.configure:

Amplify.Logging.logLevel = .verbose

@Hartistic We released a new version with the fix. https://github.com/aws-amplify/amplify-swift/releases/tag/2.42.2
Can you try the latest version and let me know if the issue is resolved.

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.