diff --git a/aws-logging-cloudwatch/api/aws-logging-cloudwatch.api b/aws-logging-cloudwatch/api/aws-logging-cloudwatch.api index 8f59d4834..0dd3fa7d6 100644 --- a/aws-logging-cloudwatch/api/aws-logging-cloudwatch.api +++ b/aws-logging-cloudwatch/api/aws-logging-cloudwatch.api @@ -3,7 +3,8 @@ public final class com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingP public fun ()V public fun (Lcom/amplifyframework/logging/cloudwatch/models/AWSCloudWatchLoggingPluginConfiguration;)V public fun (Lcom/amplifyframework/logging/cloudwatch/models/AWSCloudWatchLoggingPluginConfiguration;Lcom/amplifyframework/logging/cloudwatch/RemoteLoggingConstraintProvider;)V - public synthetic fun (Lcom/amplifyframework/logging/cloudwatch/models/AWSCloudWatchLoggingPluginConfiguration;Lcom/amplifyframework/logging/cloudwatch/RemoteLoggingConstraintProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/amplifyframework/logging/cloudwatch/models/AWSCloudWatchLoggingPluginConfiguration;Lcom/amplifyframework/logging/cloudwatch/RemoteLoggingConstraintProvider;Lcom/amplifyframework/logging/cloudwatch/models/LogStreamNameFormatter;)V + public synthetic fun (Lcom/amplifyframework/logging/cloudwatch/models/AWSCloudWatchLoggingPluginConfiguration;Lcom/amplifyframework/logging/cloudwatch/RemoteLoggingConstraintProvider;Lcom/amplifyframework/logging/cloudwatch/models/LogStreamNameFormatter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun configure (Lorg/json/JSONObject;Landroid/content/Context;)V public fun disable ()V public fun enable ()V @@ -132,6 +133,23 @@ public final class com/amplifyframework/logging/cloudwatch/models/DefaultRemoteC public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class com/amplifyframework/logging/cloudwatch/models/LogStreamContext { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/amplifyframework/logging/cloudwatch/models/LogStreamContext; + public static synthetic fun copy$default (Lcom/amplifyframework/logging/cloudwatch/models/LogStreamContext;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/amplifyframework/logging/cloudwatch/models/LogStreamContext; + public fun equals (Ljava/lang/Object;)Z + public final fun getDeviceId ()Ljava/lang/String; + public final fun getUserId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/amplifyframework/logging/cloudwatch/models/LogStreamNameFormatter { + public abstract fun format (Lcom/amplifyframework/logging/cloudwatch/models/LogStreamContext;)Ljava/lang/String; +} + public final class com/amplifyframework/logging/cloudwatch/models/LoggingConstraints { public static final field Companion Lcom/amplifyframework/logging/cloudwatch/models/LoggingConstraints$Companion; public fun ()V diff --git a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt index c39ee7f0c..3f52f791e 100644 --- a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt +++ b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt @@ -26,6 +26,7 @@ import com.amplifyframework.core.category.CategoryType import com.amplifyframework.logging.Logger import com.amplifyframework.logging.LoggingPlugin import com.amplifyframework.logging.cloudwatch.models.AWSCloudWatchLoggingPluginConfiguration +import com.amplifyframework.logging.cloudwatch.models.LogStreamNameFormatter import com.amplifyframework.logging.cloudwatch.worker.CloudwatchRouterWorker import com.amplifyframework.logging.cloudwatch.worker.CloudwatchWorkerFactory import com.amplifyframework.util.setHttpEngine @@ -39,7 +40,8 @@ import org.json.JSONObject */ class AWSCloudWatchLoggingPlugin @JvmOverloads constructor( private val awsCloudWatchLoggingPluginConfig: AWSCloudWatchLoggingPluginConfiguration? = null, - private val awsRemoteLoggingConstraintProvider: RemoteLoggingConstraintProvider? = null + private val awsRemoteLoggingConstraintProvider: RemoteLoggingConstraintProvider? = null, + private val logStreamNameFormatter: LogStreamNameFormatter? = null ) : LoggingPlugin() { private val loggingConstraintsResolver = @@ -102,8 +104,13 @@ class AWSCloudWatchLoggingPlugin @JvmOverloads constructor( ) } } - val cloudWatchLogManager = - CloudWatchLogManager(context, awsLoggingConfig, cloudWatchLogsClient, loggingConstraintsResolver) + val cloudWatchLogManager = CloudWatchLogManager( + context, + awsLoggingConfig, + cloudWatchLogsClient, + loggingConstraintsResolver, + logStreamNameFormatter = logStreamNameFormatter + ) awsCloudWatchLoggingPluginImplementation.cloudWatchLogManager = cloudWatchLogManager CloudwatchRouterWorker.workerFactories[CloudwatchRouterWorker.WORKER_FACTORY_KEY] = CloudwatchWorkerFactory( cloudWatchLogManager, diff --git a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/CloudWatchLogManager.kt b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/CloudWatchLogManager.kt index 704171dd1..18f9b9d03 100644 --- a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/CloudWatchLogManager.kt +++ b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/CloudWatchLogManager.kt @@ -35,6 +35,8 @@ import com.amplifyframework.logging.cloudwatch.db.CloudWatchLoggingDatabase import com.amplifyframework.logging.cloudwatch.db.LogEvent import com.amplifyframework.logging.cloudwatch.models.AWSCloudWatchLoggingPluginConfiguration import com.amplifyframework.logging.cloudwatch.models.CloudWatchLogEvent +import com.amplifyframework.logging.cloudwatch.models.LogStreamContext +import com.amplifyframework.logging.cloudwatch.models.LogStreamNameFormatter import com.amplifyframework.logging.cloudwatch.worker.CloudwatchLogsSyncWorker import com.amplifyframework.logging.cloudwatch.worker.CloudwatchRouterWorker import java.text.SimpleDateFormat @@ -57,7 +59,8 @@ internal class CloudWatchLogManager( private val loggingConstraintsResolver: LoggingConstraintsResolver, private val cloudWatchLoggingDatabase: CloudWatchLoggingDatabase = CloudWatchLoggingDatabase(context), private val customCognitoCredentialsProvider: CustomCognitoCredentialsProvider = CustomCognitoCredentialsProvider(), - private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO + private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO, + private val logStreamNameFormatter: LogStreamNameFormatter? = null ) { private val deviceIdKey = "unique_device_id" private var stopSync = false @@ -117,7 +120,14 @@ internal class CloudWatchLogManager( if (queriedEvents.isEmpty()) break while (queriedEvents.isNotEmpty()) { val groupName = pluginConfiguration.logGroupName - val streamName = "$todayDate.${uniqueDeviceId()}.${userIdentityId ?: "guest"}" + val deviceId = uniqueDeviceId() + val context = LogStreamContext(deviceId = deviceId, userId = userIdentityId) + + // Generate stream name: use custom formatter if provided, otherwise use default format + val streamName = logStreamNameFormatter?.format(context) + ?: // Default format: MM-dd-yyyy.deviceId.userId + "$todayDate.$deviceId.${userIdentityId ?: "guest"}" + val nextBatch = getNextBatch(queriedEvents) val inputLogEvents = nextBatch.first inputLogEventsIdToBeDeleted = nextBatch.second diff --git a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/models/LogStreamContext.kt b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/models/LogStreamContext.kt new file mode 100644 index 000000000..7167f4597 --- /dev/null +++ b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/models/LogStreamContext.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.logging.cloudwatch.models + +/** + * Contains contextual information used when generating a CloudWatch log stream name. + * This data class is passed to [LogStreamNameFormatter.format] to allow custom log stream naming. + * + * Using a data class allows for future expansion of available context data without breaking + * existing implementations. + * + * @property deviceId A unique identifier for the device + * @property userId The user's identity ID, or null if not authenticated + */ +data class LogStreamContext( + val deviceId: String, + val userId: String? +) diff --git a/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/models/LogStreamNameFormatter.kt b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/models/LogStreamNameFormatter.kt new file mode 100644 index 000000000..0ed558ab0 --- /dev/null +++ b/aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/models/LogStreamNameFormatter.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.logging.cloudwatch.models + +/** + * A functional interface for customizing CloudWatch log stream names. + * + * Implement this interface to provide custom log stream naming logic. The formatter receives + * a [LogStreamContext] containing relevant information about the device and user, which can + * be used to construct a meaningful log stream name. + * + * Example usage: + * ```kotlin + * val formatter = LogStreamNameFormatter { context -> + * "my-app-${context.deviceId}-${context.userId ?: "anonymous"}" + * } + * ``` + * + * @see LogStreamContext + */ +fun interface LogStreamNameFormatter { + /** + * Generates a log stream name based on the provided context. + * + * @param context The [LogStreamContext] containing device and user information + * @return A string to be used as the CloudWatch log stream name + */ + fun format(context: LogStreamContext): String +}