Skip to content

Commit 7044bad

Browse files
committed
feat(logging): Add support for custom log stream name formatters
1 parent 232cc21 commit 7044bad

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/AWSCloudWatchLoggingPlugin.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.amplifyframework.core.category.CategoryType
2626
import com.amplifyframework.logging.Logger
2727
import com.amplifyframework.logging.LoggingPlugin
2828
import com.amplifyframework.logging.cloudwatch.models.AWSCloudWatchLoggingPluginConfiguration
29+
import com.amplifyframework.logging.cloudwatch.models.LogStreamNameFormatter
2930
import com.amplifyframework.logging.cloudwatch.worker.CloudwatchRouterWorker
3031
import com.amplifyframework.logging.cloudwatch.worker.CloudwatchWorkerFactory
3132
import com.amplifyframework.util.setHttpEngine
@@ -39,7 +40,8 @@ import org.json.JSONObject
3940
*/
4041
class AWSCloudWatchLoggingPlugin @JvmOverloads constructor(
4142
private val awsCloudWatchLoggingPluginConfig: AWSCloudWatchLoggingPluginConfiguration? = null,
42-
private val awsRemoteLoggingConstraintProvider: RemoteLoggingConstraintProvider? = null
43+
private val awsRemoteLoggingConstraintProvider: RemoteLoggingConstraintProvider? = null,
44+
private val logStreamNameFormatter: LogStreamNameFormatter? = null
4345
) : LoggingPlugin<CloudWatchLogsClient>() {
4446

4547
private val loggingConstraintsResolver =
@@ -102,8 +104,13 @@ class AWSCloudWatchLoggingPlugin @JvmOverloads constructor(
102104
)
103105
}
104106
}
105-
val cloudWatchLogManager =
106-
CloudWatchLogManager(context, awsLoggingConfig, cloudWatchLogsClient, loggingConstraintsResolver)
107+
val cloudWatchLogManager = CloudWatchLogManager(
108+
context,
109+
awsLoggingConfig,
110+
cloudWatchLogsClient,
111+
loggingConstraintsResolver,
112+
logStreamNameFormatter = logStreamNameFormatter
113+
)
107114
awsCloudWatchLoggingPluginImplementation.cloudWatchLogManager = cloudWatchLogManager
108115
CloudwatchRouterWorker.workerFactories[CloudwatchRouterWorker.WORKER_FACTORY_KEY] = CloudwatchWorkerFactory(
109116
cloudWatchLogManager,

aws-logging-cloudwatch/src/main/java/com/amplifyframework/logging/cloudwatch/CloudWatchLogManager.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import com.amplifyframework.logging.cloudwatch.db.CloudWatchLoggingDatabase
3535
import com.amplifyframework.logging.cloudwatch.db.LogEvent
3636
import com.amplifyframework.logging.cloudwatch.models.AWSCloudWatchLoggingPluginConfiguration
3737
import com.amplifyframework.logging.cloudwatch.models.CloudWatchLogEvent
38+
import com.amplifyframework.logging.cloudwatch.models.LogStreamContext
39+
import com.amplifyframework.logging.cloudwatch.models.LogStreamNameFormatter
3840
import com.amplifyframework.logging.cloudwatch.worker.CloudwatchLogsSyncWorker
3941
import com.amplifyframework.logging.cloudwatch.worker.CloudwatchRouterWorker
4042
import java.text.SimpleDateFormat
@@ -57,7 +59,8 @@ internal class CloudWatchLogManager(
5759
private val loggingConstraintsResolver: LoggingConstraintsResolver,
5860
private val cloudWatchLoggingDatabase: CloudWatchLoggingDatabase = CloudWatchLoggingDatabase(context),
5961
private val customCognitoCredentialsProvider: CustomCognitoCredentialsProvider = CustomCognitoCredentialsProvider(),
60-
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO
62+
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
63+
private val logStreamNameFormatter: LogStreamNameFormatter? = null
6164
) {
6265
private val deviceIdKey = "unique_device_id"
6366
private var stopSync = false
@@ -117,7 +120,14 @@ internal class CloudWatchLogManager(
117120
if (queriedEvents.isEmpty()) break
118121
while (queriedEvents.isNotEmpty()) {
119122
val groupName = pluginConfiguration.logGroupName
120-
val streamName = "$todayDate.${uniqueDeviceId()}.${userIdentityId ?: "guest"}"
123+
val deviceId = uniqueDeviceId()
124+
val context = LogStreamContext(deviceId = deviceId, userId = userIdentityId)
125+
126+
// Generate stream name: use custom formatter if provided, otherwise use default format
127+
val streamName = logStreamNameFormatter?.format(context)
128+
?: // Default format: MM-dd-yyyy.deviceId.userId
129+
"$todayDate.$deviceId.${userIdentityId ?: "guest"}"
130+
121131
val nextBatch = getNextBatch(queriedEvents)
122132
val inputLogEvents = nextBatch.first
123133
inputLogEventsIdToBeDeleted = nextBatch.second
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amplifyframework.logging.cloudwatch.models
16+
17+
/**
18+
* Contains contextual information used when generating a CloudWatch log stream name.
19+
* This data class is passed to [LogStreamNameFormatter.format] to allow custom log stream naming.
20+
*
21+
* Using a data class allows for future expansion of available context data without breaking
22+
* existing implementations.
23+
*
24+
* @property deviceId A unique identifier for the device
25+
* @property userId The user's identity ID, or null if not authenticated
26+
*/
27+
data class LogStreamContext(
28+
val deviceId: String,
29+
val userId: String?
30+
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amplifyframework.logging.cloudwatch.models
16+
17+
/**
18+
* A functional interface for customizing CloudWatch log stream names.
19+
*
20+
* Implement this interface to provide custom log stream naming logic. The formatter receives
21+
* a [LogStreamContext] containing relevant information about the device and user, which can
22+
* be used to construct a meaningful log stream name.
23+
*
24+
* Example usage:
25+
* ```kotlin
26+
* val formatter = LogStreamNameFormatter { context ->
27+
* "my-app-${context.deviceId}-${context.userId ?: "anonymous"}"
28+
* }
29+
* ```
30+
*
31+
* @see LogStreamContext
32+
*/
33+
fun interface LogStreamNameFormatter {
34+
/**
35+
* Generates a log stream name based on the provided context.
36+
*
37+
* @param context The [LogStreamContext] containing device and user information
38+
* @return A string to be used as the CloudWatch log stream name
39+
*/
40+
fun format(context: LogStreamContext): String
41+
}

0 commit comments

Comments
 (0)