diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java index 7deca89f1..27b69d5ad 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java @@ -82,18 +82,29 @@ static String getVersionFromProperties(String propertyFileName, String versionKe /** * Configures the AWS SDK to use Powertools user agent by setting the sdk.ua.appId system property. - * If the property is already set and not empty, appends the Powertools user agent with a "/" separator. + * Preserves any user-provided value and replaces any existing Powertools user agent. + * Enforces a 50 character limit to comply with AWS SDK recommendations. * This should be called during library initialization to ensure the user agent is properly configured. */ public static void configureUserAgent(String ptFeature) { try { String existingValue = System.getProperty(SDK_USER_AGENT_APP_ID); String powertoolsUserAgent = getUserAgent(ptFeature); + String newValue; - if (existingValue != null && !existingValue.isEmpty()) { - System.setProperty(SDK_USER_AGENT_APP_ID, existingValue + "/" + powertoolsUserAgent); + if (existingValue == null || existingValue.isEmpty()) { + newValue = powertoolsUserAgent; } else { - System.setProperty(SDK_USER_AGENT_APP_ID, powertoolsUserAgent); + String userValue = extractUserValue(existingValue); + if (userValue.isEmpty()) { + newValue = powertoolsUserAgent; + } else { + newValue = userValue + "/" + powertoolsUserAgent; + } + } + + if (newValue.length() <= 50) { + System.setProperty(SDK_USER_AGENT_APP_ID, newValue); } } catch (Exception e) { // We don't re-raise since we don't want to break the user if something in this logic doesn't work @@ -101,6 +112,22 @@ public static void configureUserAgent(String ptFeature) { } } + /** + * Extracts the user-provided value from the existing user agent string by removing any Powertools user agent. + * A Powertools user agent follows the pattern "PT/{FEATURE}/{VERSION} PTENV/{ENV}". + * + * @param existingValue the existing user agent string + * @return the user-provided value without Powertools user agent, or empty string if none exists + */ + static String extractUserValue(String existingValue) { + if (existingValue == null || existingValue.isEmpty()) { + return ""; + } + // Remove Powertools user agent pattern: PT/{FEATURE}/{VERSION} PTENV/{ENV} + String result = existingValue.replaceAll("/?PT/[^/]+/[^ ]+ PTENV/[^ ]+", ""); + return result.trim(); + } + /** * Retrieves the user agent string for the Powertools for AWS Lambda. * It follows the pattern PT/{PT_FEATURE}/{PT_VERSION} PTENV/{PT_EXEC_ENV} diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java index fbe4529d8..33050d8b4 100644 --- a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/UserAgentConfiguratorTest.java @@ -131,7 +131,7 @@ void testConfigureUserAgent() { } @Test - void testConfigureUserAgent_WithExistingValue() { + void testConfigureUserAgent_WithExistingUserValue() { System.setProperty("sdk.ua.appId", "UserValueABC123"); UserAgentConfigurator.configureUserAgent("test-feature"); @@ -139,6 +139,64 @@ void testConfigureUserAgent_WithExistingValue() { .isEqualTo("UserValueABC123/PT/TEST-FEATURE/" + VERSION + " PTENV/NA"); } + @Test + void testConfigureUserAgent_ReplacePowertoolsUserAgent() { + System.setProperty("sdk.ua.appId", "PT/BATCH/" + VERSION + " PTENV/NA"); + UserAgentConfigurator.configureUserAgent("logging-log4j"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/LOGGING-LOG4J/" + VERSION + " PTENV/NA"); + } + + @Test + void testConfigureUserAgent_PreserveUserValueAndReplacePowertools() { + System.setProperty("sdk.ua.appId", "UserValue/PT/BATCH/" + VERSION + " PTENV/NA"); + UserAgentConfigurator.configureUserAgent("tracing"); + + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("UserValue/PT/TRACING/" + VERSION + " PTENV/NA"); + } + + @Test + void testConfigureUserAgent_ExceedsLimit() { + System.setProperty("sdk.ua.appId", "VeryLongUserValueThatExceedsTheLimitWhenCombined"); + UserAgentConfigurator.configureUserAgent("test-feature"); + + // Should not update if it would exceed 50 characters + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("VeryLongUserValueThatExceedsTheLimitWhenCombined"); + } + + @Test + void testExtractUserValue_NoUserValue() { + String result = UserAgentConfigurator.extractUserValue("PT/BATCH/" + VERSION + " PTENV/NA"); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUserValue_WithUserValue() { + String result = UserAgentConfigurator.extractUserValue("UserValue/PT/BATCH/" + VERSION + " PTENV/NA"); + assertThat(result).isEqualTo("UserValue"); + } + + @Test + void testExtractUserValue_EmptyString() { + String result = UserAgentConfigurator.extractUserValue(""); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUserValue_NullString() { + String result = UserAgentConfigurator.extractUserValue(null); + assertThat(result).isEmpty(); + } + + @Test + void testExtractUserValue_OnlyUserValue() { + String result = UserAgentConfigurator.extractUserValue("MyCustomValue"); + assertThat(result).isEqualTo("MyCustomValue"); + } + @Test void testConfigureUserAgent_WithEmptyExistingValue() { System.setProperty("sdk.ua.appId", ""); @@ -148,4 +206,25 @@ void testConfigureUserAgent_WithEmptyExistingValue() { .isEqualTo("PT/TEST-FEATURE/" + VERSION + " PTENV/NA"); } + @Test + @SetEnvironmentVariable(key = AWS_EXECUTION_ENV, value = "AWS_Lambda_java11") + void testConfigureUserAgent_MultipleUtilities() { + System.clearProperty("sdk.ua.appId"); + + // First utility + UserAgentConfigurator.configureUserAgent("batch"); + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/BATCH/" + VERSION + " PTENV/AWS_Lambda_java11"); + + // Second utility - should replace, not append + UserAgentConfigurator.configureUserAgent("logging-log4j"); + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/LOGGING-LOG4J/" + VERSION + " PTENV/AWS_Lambda_java11"); + + // Third utility - should replace again + UserAgentConfigurator.configureUserAgent("tracing"); + assertThat(System.getProperty("sdk.ua.appId")) + .isEqualTo("PT/TRACING/" + VERSION + " PTENV/AWS_Lambda_java11"); + } + }