From 2b4b6a3aa2e71b0ff505b5570496d003eb128f11 Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Wed, 4 Feb 2026 17:19:36 +0100 Subject: [PATCH 1/9] feat(logging): add support for key-value pairs in LambdaJsonEncoder --- .../powertools/logging/logback/JsonUtils.java | 6 ++ .../logging/logback/LambdaJsonEncoder.java | 20 ++++--- .../internal/LambdaJsonEncoderTest.java | 59 +++++++++++++++++++ 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java index 67d6b268d..fbdec8e4a 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java @@ -73,6 +73,12 @@ static void serializeMDCEntry(Map.Entry entry, JsonSerializer se } } + static void serializeKVPEntry(String key, Object value, JsonSerializer serializer) { + serializer.writeRaw(','); + serializer.writeFieldName(key); + serializer.writeObject(value); + } + static void serializeArguments(ILoggingEvent event, JsonSerializer serializer) throws IOException { Object[] arguments = event.getArgumentArray(); if (arguments != null) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java index 9afaf0ab7..894c3081b 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java @@ -15,10 +15,7 @@ package software.amazon.lambda.powertools.logging.logback; import static java.nio.charset.StandardCharsets.UTF_8; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeArguments; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeMDCEntries; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeMDCEntry; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeTimestamp; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.*; import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; import ch.qos.logback.classic.pattern.ThrowableProxyConverter; @@ -27,10 +24,8 @@ import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.encoder.EncoderBase; import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; + import software.amazon.lambda.powertools.logging.internal.JsonSerializer; import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; @@ -88,6 +83,8 @@ public byte[] encode(ILoggingEvent event) { serializeMDCEntries(sortedMap, serializer); + serializeKeyValuePairs(event, serializer); + serializeArguments(event, serializer); serializeThreadInfo(event, serializer); @@ -104,6 +101,13 @@ public byte[] encode(ILoggingEvent event) { return builder.toString().getBytes(UTF_8); } + private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializer) { + Optional.ofNullable(event.getKeyValuePairs()) + .orElse(Collections.emptyList()).stream() + .filter(Objects::nonNull) + .forEach(kvp -> serializeKVPEntry(String.valueOf(kvp.key), kvp.value, serializer)); + } + private void serializeThreadInfo(ILoggingEvent event, JsonSerializer serializer) { if (includeThreadInfo) { if (event.getThreadName() != null) { diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index 16bd9e92a..7e77b3e89 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -40,6 +40,7 @@ import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.List; import java.util.Collections; import java.util.Date; import java.util.TimeZone; @@ -50,6 +51,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.slf4j.event.KeyValuePair; import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -442,4 +444,61 @@ void shouldLogException() { .contains("\"stack\":\"java.lang.IllegalStateException: Unexpected value\\n"); } + @Test + void shouldLogKeyValuePairs() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + encoder.start(); + + Object[] arguments = { + "argument_01", + StructuredArguments.entry("structured_argument_01_retain", "retained"), + StructuredArguments.entry("structured_argument_02_overwrite", "to_be_overwritten") + }; + LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test with argument: {}", + null, arguments); + + MDC.put("mdc_01_retain", "retained"); + MDC.put("mdc_02_overwrite", "to_be_overwritten"); + + keyValuePairsLoggingEvent.setKeyValuePairs(List.of( + new KeyValuePair("key_01_string", "value_01"), + new KeyValuePair("key_02_numeric", 2), + new KeyValuePair("key_03_decimal", 2.333), + new KeyValuePair("key_04_null", null), + new KeyValuePair("", "value_05_empty_key"), + new KeyValuePair(null, "value_06_null_key"), + new KeyValuePair("key_07_boolean_true", true), + new KeyValuePair("key_08_boolean_false", false), + new KeyValuePair("mdc_02_overwrite", "overwritten_by_kvp"), + new KeyValuePair("structured_argument_02_overwrite", "overwritten_by_kvp") + )); + + // WHEN + byte[] encoded = encoder.encode(keyValuePairsLoggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result) + // Arguments + .contains("Key Value Pairs Test with argument: argument_01") + .contains("\"structured_argument_01_retain\":\"retained\"") + // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // MDC + .contains("\"mdc_01_retain\":\"retained\"") + // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // Key Value Pairs + .contains("\"key_01_string\":\"value_01\"") + .contains("\"key_02_numeric\":2") + .contains("\"key_03_decimal\":2.333") + .contains("\"key_04_null\":null") + .contains("\"\":\"value_05_empty_key\"") + .contains("\"null\":\"value_06_null_key\"") + .contains("\"key_07_boolean_true\":true") + .contains("\"key_08_boolean_false\":false") + .contains("\"mdc_02_overwrite\":\"overwritten_by_kvp\"") + .contains("\"structured_argument_02_overwrite\":\"overwritten_by_kvp\"") + ; + } + } From c7b782d92439d2a6630c91c05a2e8c9cfc18323a Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Wed, 4 Feb 2026 17:19:36 +0100 Subject: [PATCH 2/9] feat(logging): add support for key-value pairs in LambdaEcsEncoder --- .../logging/logback/LambdaEcsEncoder.java | 17 ++++-- .../internal/LambdaEcsEncoderTest.java | 61 +++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java index 6a82d8e67..d9d90b09b 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java @@ -23,9 +23,7 @@ import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeArguments; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeMDCEntries; -import static software.amazon.lambda.powertools.logging.logback.JsonUtils.serializeTimestamp; +import static software.amazon.lambda.powertools.logging.logback.JsonUtils.*; import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; import ch.qos.logback.classic.pattern.ThrowableProxyConverter; @@ -34,8 +32,8 @@ import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.encoder.EncoderBase; import java.io.IOException; -import java.util.Arrays; -import java.util.Map; +import java.util.*; + import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.logging.internal.JsonSerializer; @@ -132,6 +130,8 @@ public byte[] encode(ILoggingEvent event) { serializeMDCEntries(mdcPropertyMap, serializer); + serializeKeyValuePairs(event, serializer); + serializeArguments(event, serializer); serializer.writeEndObject(); @@ -142,6 +142,13 @@ public byte[] encode(ILoggingEvent event) { return builder.toString().getBytes(UTF_8); } + private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializer) { + Optional.ofNullable(event.getKeyValuePairs()) + .orElse(Collections.emptyList()).stream() + .filter(Objects::nonNull) + .forEach(kvp -> serializeKVPEntry(String.valueOf(kvp.key), kvp.value, serializer)); + } + private void serializeFunctionInfo(JsonSerializer serializer, String arn, Map mdcPropertyMap) { if (includeFaasInfo) { serializer.writeRaw(','); diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index 30ede8ba8..ff7c086ff 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -25,6 +25,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.AfterEach; @@ -40,11 +41,14 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter; import ch.qos.logback.classic.spi.LoggingEvent; +import org.slf4j.event.KeyValuePair; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.stubs.TestLambdaContext; import software.amazon.lambda.powertools.logging.PowertoolsLogging; +import software.amazon.lambda.powertools.logging.argument.StructuredArguments; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; import software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder; +import software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder; @Order(3) class LambdaEcsEncoderTest { @@ -179,4 +183,61 @@ private void setMDC() { MDC.put(PowertoolsLoggedFields.CORRELATION_ID.getName(), "test-correlation-id"); } + @Test + void shouldLogKeyValuePairs() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + encoder.start(); + + Object[] arguments = { + "argument_01", + StructuredArguments.entry("structured_argument_01_retain", "retained"), + StructuredArguments.entry("structured_argument_02_overwrite", "to_be_overwritten") + }; + LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test with argument: {}", + null, arguments); + + MDC.put("mdc_01_retain", "retained"); + MDC.put("mdc_02_overwrite", "to_be_overwritten"); + + keyValuePairsLoggingEvent.setKeyValuePairs(List.of( + new KeyValuePair("key_01_string", "value_01"), + new KeyValuePair("key_02_numeric", 2), + new KeyValuePair("key_03_decimal", 2.333), + new KeyValuePair("key_04_null", null), + new KeyValuePair("", "value_05_empty_key"), + new KeyValuePair(null, "value_06_null_key"), + new KeyValuePair("key_07_boolean_true", true), + new KeyValuePair("key_08_boolean_false", false), + new KeyValuePair("mdc_02_overwrite", "overwritten_by_kvp"), + new KeyValuePair("structured_argument_02_overwrite", "overwritten_by_kvp") + )); + + // WHEN + byte[] encoded = encoder.encode(keyValuePairsLoggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result) + // Arguments + .contains("Key Value Pairs Test with argument: argument_01") + .contains("\"structured_argument_01_retain\":\"retained\"") + // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // MDC + .contains("\"mdc_01_retain\":\"retained\"") + // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // Key Value Pairs + .contains("\"key_01_string\":\"value_01\"") + .contains("\"key_02_numeric\":2") + .contains("\"key_03_decimal\":2.333") + .contains("\"key_04_null\":null") + .contains("\"\":\"value_05_empty_key\"") + .contains("\"null\":\"value_06_null_key\"") + .contains("\"key_07_boolean_true\":true") + .contains("\"key_08_boolean_false\":false") + .contains("\"mdc_02_overwrite\":\"overwritten_by_kvp\"") + .contains("\"structured_argument_02_overwrite\":\"overwritten_by_kvp\"") + ; + } + } From c694ff82116fba68cc51cff79e37552f6f9fc8e0 Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sat, 14 Feb 2026 23:01:45 +0100 Subject: [PATCH 3/9] feat(logging): add e2e tests for fluent api --- .../handlers/logging-log4j-fluent-api/pom.xml | 82 +++++++++++++++++++ .../lambda/powertools/e2e/Function.java | 41 ++++++++++ .../amazon/lambda/powertools/e2e/Input.java | 38 +++++++++ .../aws-lambda-java-core/reflect-config.json | 13 +++ .../reflect-config.json | 35 ++++++++ .../jni-config.json | 11 +++ .../native-image.properties | 1 + .../reflect-config.json | 62 ++++++++++++++ .../resource-config.json | 19 +++++ .../reflect-config.json | 25 ++++++ .../reflect-config.json | 20 +++++ .../resource-config.json | 7 ++ .../src/main/resources/log4j2.xml | 17 ++++ .../logging-logback-fluent-api/pom.xml | 82 +++++++++++++++++++ .../lambda/powertools/e2e/Function.java | 41 ++++++++++ .../amazon/lambda/powertools/e2e/Input.java | 38 +++++++++ .../aws-lambda-java-core/reflect-config.json | 13 +++ .../reflect-config.json | 35 ++++++++ .../jni-config.json | 11 +++ .../native-image.properties | 1 + .../reflect-config.json | 62 ++++++++++++++ .../resource-config.json | 19 +++++ .../reflect-config.json | 25 ++++++ .../reflect-config.json | 20 +++++ .../resource-config.json | 7 ++ .../src/main/resources/logback.xml | 16 ++++ powertools-e2e-tests/handlers/pom.xml | 2 + .../amazon/lambda/powertools/LoggingE2ET.java | 2 +- 28 files changed, 744 insertions(+), 1 deletion(-) create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/pom.xml create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json create mode 100644 powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/log4j2.xml create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/pom.xml create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json create mode 100644 powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/logback.xml diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/pom.xml b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/pom.xml new file mode 100644 index 000000000..8432aa604 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + + software.amazon.lambda + e2e-test-handlers-parent + 2.9.0 + + + e2e-test-handler-logging-log4j-fluent-api + jar + E2E test handler – Logging Log4j Fluent API + + + + software.amazon.lambda + powertools-logging-log4j + + + org.aspectj + aspectjrt + + + com.amazonaws + aws-lambda-java-events + + + com.amazonaws + aws-lambda-java-runtime-interface-client + + + com.amazonaws + aws-lambda-java-core + + + + + + + dev.aspectj + aspectj-maven-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-logging + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + + + + + native-image + + + + org.graalvm.buildtools + native-maven-plugin + + + + + + diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..fbd6d7286 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.LoggingEventBuilder; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; + +public class Function implements RequestHandler { + private static final Logger LOG = LoggerFactory.getLogger(Function.class); + + @Logging + public String handleRequest(Input input, Context context) { + + LoggingEventBuilder loggingEventBuilder = LOG.atInfo().setMessage(input.getMessage()); + //noinspection ResultOfMethodCallIgnored + input.getKeys().forEach(loggingEventBuilder::addKeyValue); + loggingEventBuilder.log(); + + // Flush buffer manually since we buffer at INFO level to test log buffering + PowertoolsLogging.flushBuffer(); + + return "OK"; + } +} diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..66fd49ddc --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private String message; + private Map keys; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getKeys() { + return keys; + } + + public void setKeys(Map keys) { + this.keys = keys; + } +} diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..be6aac3f6 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlog4j2.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/log4j2.xml b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/log4j2.xml new file mode 100644 index 000000000..28e03a9e0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-log4j-fluent-api/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/pom.xml b/powertools-e2e-tests/handlers/logging-logback-fluent-api/pom.xml new file mode 100644 index 000000000..83d0abddb --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + + software.amazon.lambda + e2e-test-handlers-parent + 2.9.0 + + + e2e-test-handler-logging-logback-fluent-api + jar + E2E test handler – Logging Logback Fluent API + + + + software.amazon.lambda + powertools-logging-logback + + + org.aspectj + aspectjrt + + + com.amazonaws + aws-lambda-java-events + + + com.amazonaws + aws-lambda-java-runtime-interface-client + + + com.amazonaws + aws-lambda-java-core + + + + + + + dev.aspectj + aspectj-maven-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-logging + + + + + + + compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + + + + + native-image + + + + org.graalvm.buildtools + native-maven-plugin + + + + + + diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java new file mode 100644 index 000000000..fbd6d7286 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 software.amazon.lambda.powertools.e2e; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.LoggingEventBuilder; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.PowertoolsLogging; + +public class Function implements RequestHandler { + private static final Logger LOG = LoggerFactory.getLogger(Function.class); + + @Logging + public String handleRequest(Input input, Context context) { + + LoggingEventBuilder loggingEventBuilder = LOG.atInfo().setMessage(input.getMessage()); + //noinspection ResultOfMethodCallIgnored + input.getKeys().forEach(loggingEventBuilder::addKeyValue); + loggingEventBuilder.log(); + + // Flush buffer manually since we buffer at INFO level to test log buffering + PowertoolsLogging.flushBuffer(); + + return "OK"; + } +} diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java new file mode 100644 index 000000000..66fd49ddc --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/java/software/amazon/lambda/powertools/e2e/Input.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 software.amazon.lambda.powertools.e2e; + +import java.util.Map; + +public class Input { + private String message; + private Map keys; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Map getKeys() { + return keys; + } + + public void setKeys(Map keys) { + this.keys = keys; + } +} diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json new file mode 100644 index 000000000..2780aca09 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-core/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntime", + "methods":[{"name":"","parameterTypes":[] }], + "fields":[{"name":"logger"}], + "allPublicMethods":true + }, + { + "name":"com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal", + "methods":[{"name":"","parameterTypes":[] }], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json new file mode 100644 index 000000000..ddda5d5f1 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-events/reflect-config.json @@ -0,0 +1,35 @@ +[ + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$ProxyRequestContext", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent$RequestIdentity", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json new file mode 100644 index 000000000..91be72f7a --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/jni-config.json @@ -0,0 +1,11 @@ +[ + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientException", + "methods":[{"name":"","parameterTypes":["java.lang.String","int"] }] + }, + { + "name":"com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields":[{"name":"id"}, {"name":"invokedFunctionArn"}, {"name":"deadlineTimeInMs"}, {"name":"xrayTraceId"}, {"name":"clientContext"}, {"name":"cognitoIdentity"}, {"name": "tenantId"}, {"name":"content"}], + "allPublicMethods":true + } +] \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties new file mode 100644 index 000000000..20f8b7801 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/native-image.properties @@ -0,0 +1 @@ +Args = --initialize-at-build-time=jdk.xml.internal.SecuritySupport \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json new file mode 100644 index 000000000..467af67a0 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/reflect-config.json @@ -0,0 +1,62 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.LambdaRuntime", + "fields": [{ "name": "logger" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogLevel", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "com.amazonaws.services.lambda.runtime.logging.LogFormat", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true + }, + { + "name": "java.lang.Void", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "java.util.Collections$UnmodifiableMap", + "fields": [{ "name": "m" }] + }, + { + "name": "jdk.internal.module.IllegalAccessLogger", + "fields": [{ "name": "logger" }] + }, + { + "name": "sun.misc.Unsafe", + "fields": [{ "name": "theUnsafe" }] + }, + { + "name": "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest", + "fields": [ + { "name": "id" }, + { "name": "invokedFunctionArn" }, + { "name": "deadlineTimeInMs" }, + { "name": "xrayTraceId" }, + { "name": "clientContext" }, + { "name": "cognitoIdentity" }, + { "name": "tenantId" }, + { "name": "content" } + ], + "allPublicMethods": true, + "unsafeAllocated": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json new file mode 100644 index 000000000..1062b4249 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-runtime-interface-client/resource-config.json @@ -0,0 +1,19 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux-x86_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-aarch_64.so\\E" + }, + { + "pattern": "\\Qjni/libaws-lambda-jni.linux_musl-x86_64.so\\E" + } + ] + }, + "bundles": [] +} \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json new file mode 100644 index 000000000..9890688f9 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/com.amazonaws/aws-lambda-java-serialization/reflect-config.json @@ -0,0 +1,25 @@ +[ + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.deser.Deserializers[]" + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7HandlersImpl", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [{ "name": "", "parameterTypes": [] }] + }, + { + "name": "com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ser.Serializers[]" + }, + { + "name": "org.joda.time.DateTime", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json new file mode 100644 index 000000000..9ddd235e2 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "software.amazon.lambda.powertools.e2e.Function", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "software.amazon.lambda.powertools.e2e.Input", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json new file mode 100644 index 000000000..a603a9398 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/META-INF/native-image/software.amazon.lambda.powertools.e2e/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\Qlogback.xml\\E" + }]}, + "bundles":[] +} diff --git a/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/logback.xml b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/logback.xml new file mode 100644 index 000000000..0a5e4d146 --- /dev/null +++ b/powertools-e2e-tests/handlers/logging-logback-fluent-api/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + + + + INFO + + + + + + + \ No newline at end of file diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 585bb83db..c9a4826a2 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -30,7 +30,9 @@ largemessage-functional largemessage_idempotent logging-log4j + logging-log4j-fluent-api logging-logback + logging-logback-fluent-api logging-functional tracing metrics diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java index 20bc5394d..364029214 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java @@ -70,7 +70,7 @@ void tearDown() { } @ParameterizedTest - @ValueSource(strings = { "logging-log4j", "logging-logback", "logging-functional" }) + @ValueSource(strings = { "logging-log4j", "logging-log4j-fluent-api", "logging-logback", "logging-logback-fluent-api", "logging-functional" }) @Timeout(value = 15, unit = TimeUnit.MINUTES) void test_logInfoWithAdditionalKeys(String pathToFunction) throws JsonProcessingException { setupInfrastructure(pathToFunction); From 1db3bd4bf766a41c78f2e9ef759115ef1bd3c151 Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sat, 14 Feb 2026 23:01:45 +0100 Subject: [PATCH 4/9] feat(logging): cleanup, remove unused import --- .../lambda/powertools/logging/internal/LambdaEcsEncoderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index ff7c086ff..91df65140 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -48,7 +48,6 @@ import software.amazon.lambda.powertools.logging.argument.StructuredArguments; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; import software.amazon.lambda.powertools.logging.logback.LambdaEcsEncoder; -import software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder; @Order(3) class LambdaEcsEncoderTest { From 863ae5b430a6a7f78e216432a56070dff71fa31b Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sun, 15 Feb 2026 09:30:59 +0100 Subject: [PATCH 5/9] feat(logging/logback): exclude Powertools reserved keys from key-value pairs and sort remaining entries to match existing MDC handling behavior --- .../powertools/logging/logback/LambdaEcsEncoder.java | 6 +++++- .../powertools/logging/logback/LambdaJsonEncoder.java | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java index d9d90b09b..bfe0707b1 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java @@ -36,6 +36,7 @@ import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.logging.internal.JsonSerializer; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; /** @@ -146,7 +147,10 @@ private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializ Optional.ofNullable(event.getKeyValuePairs()) .orElse(Collections.emptyList()).stream() .filter(Objects::nonNull) - .forEach(kvp -> serializeKVPEntry(String.valueOf(kvp.key), kvp.value, serializer)); + .map(kvp -> new AbstractMap.SimpleEntry<>(String.valueOf(kvp.key), kvp.value)) + .filter(kvp -> !PowertoolsLoggedFields.stringValues().contains(kvp.getKey())) + .sorted(Comparator.comparing(AbstractMap.SimpleEntry::getKey)) + .forEach(kvp -> serializeKVPEntry(kvp.getKey(), kvp.getValue(), serializer)); } private void serializeFunctionInfo(JsonSerializer serializer, String arn, Map mdcPropertyMap) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java index 894c3081b..84f355036 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java @@ -103,9 +103,12 @@ public byte[] encode(ILoggingEvent event) { private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializer) { Optional.ofNullable(event.getKeyValuePairs()) - .orElse(Collections.emptyList()).stream() - .filter(Objects::nonNull) - .forEach(kvp -> serializeKVPEntry(String.valueOf(kvp.key), kvp.value, serializer)); + .orElse(Collections.emptyList()).stream() + .filter(Objects::nonNull) + .map(kvp -> new AbstractMap.SimpleEntry<>(String.valueOf(kvp.key), kvp.value)) + .filter(kvp -> !PowertoolsLoggedFields.stringValues().contains(kvp.getKey())) + .sorted(Comparator.comparing(AbstractMap.SimpleEntry::getKey)) + .forEach(kvp -> serializeKVPEntry(kvp.getKey(), kvp.getValue(), serializer)); } private void serializeThreadInfo(ILoggingEvent event, JsonSerializer serializer) { From 01ca395dbcc95375bbcdee9bcd31e7e53511df0a Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sun, 15 Feb 2026 09:30:59 +0100 Subject: [PATCH 6/9] feat(logging/logback): fix typos --- .../powertools/logging/internal/LambdaEcsEncoderTest.java | 4 ++-- .../powertools/logging/internal/LambdaJsonEncoderTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index 91df65140..473bbd7c8 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -221,10 +221,10 @@ void shouldLogKeyValuePairs() { // Arguments .contains("Key Value Pairs Test with argument: argument_01") .contains("\"structured_argument_01_retain\":\"retained\"") - // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for arguments // MDC .contains("\"mdc_01_retain\":\"retained\"") - // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for MDC // Key Value Pairs .contains("\"key_01_string\":\"value_01\"") .contains("\"key_02_numeric\":2") diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index 7e77b3e89..87aef30ca 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -483,10 +483,10 @@ void shouldLogKeyValuePairs() { // Arguments .contains("Key Value Pairs Test with argument: argument_01") .contains("\"structured_argument_01_retain\":\"retained\"") - // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for arguments // MDC .contains("\"mdc_01_retain\":\"retained\"") - // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") TODO: Deduplication not implemented vor Arguments + // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for MDC // Key Value Pairs .contains("\"key_01_string\":\"value_01\"") .contains("\"key_02_numeric\":2") From d34d4de1448d568808853fb5101c56343fc4fa00 Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sun, 15 Feb 2026 09:30:59 +0100 Subject: [PATCH 7/9] feat(logging/logback): add logging docs regarding SLF4J Fluent Logging API --- docs/core/logging.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/core/logging.md b/docs/core/logging.md index 8358087d2..f950c81df 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -378,6 +378,10 @@ You can use Powertools for AWS Lambda Logging with either the `@Logging` annotat } ``` +???+ warning "SLF4J Fluent Logging API" + When using [SLF4J Fluent Logging API](https://www.slf4j.org/manual.html#fluent) **do not add reserved keys** listed in [standard structured keys](#standard-structured-keys) and [additional structured keys](#additional-structured-keys) as key-value pairs via `addKeyValue`. + This may cause unindented behavior. + ## Standard structured keys Your logs will always include the following keys in your structured logging: From bff7b3a9db7bf874299b9e7cde0d4bc0d194115c Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sun, 15 Feb 2026 09:30:59 +0100 Subject: [PATCH 8/9] feat(logging/logback): remove test cases related to key-value pair deduplication and refactor --- .../logging/logback/LambdaEcsEncoder.java | 2 +- .../logging/logback/LambdaJsonEncoder.java | 2 +- .../internal/LambdaEcsEncoderTest.java | 24 ++----------------- .../internal/LambdaJsonEncoderTest.java | 24 ++----------------- 4 files changed, 6 insertions(+), 46 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java index bfe0707b1..6eab1d7ec 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java @@ -149,7 +149,7 @@ private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializ .filter(Objects::nonNull) .map(kvp -> new AbstractMap.SimpleEntry<>(String.valueOf(kvp.key), kvp.value)) .filter(kvp -> !PowertoolsLoggedFields.stringValues().contains(kvp.getKey())) - .sorted(Comparator.comparing(AbstractMap.SimpleEntry::getKey)) + .sorted(Map.Entry.comparingByKey()) .forEach(kvp -> serializeKVPEntry(kvp.getKey(), kvp.getValue(), serializer)); } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java index 84f355036..469fc2222 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java @@ -107,7 +107,7 @@ private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializ .filter(Objects::nonNull) .map(kvp -> new AbstractMap.SimpleEntry<>(String.valueOf(kvp.key), kvp.value)) .filter(kvp -> !PowertoolsLoggedFields.stringValues().contains(kvp.getKey())) - .sorted(Comparator.comparing(AbstractMap.SimpleEntry::getKey)) + .sorted(Map.Entry.comparingByKey()) .forEach(kvp -> serializeKVPEntry(kvp.getKey(), kvp.getValue(), serializer)); } diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index 473bbd7c8..ac8cf8ace 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -188,16 +188,8 @@ void shouldLogKeyValuePairs() { LambdaEcsEncoder encoder = new LambdaEcsEncoder(); encoder.start(); - Object[] arguments = { - "argument_01", - StructuredArguments.entry("structured_argument_01_retain", "retained"), - StructuredArguments.entry("structured_argument_02_overwrite", "to_be_overwritten") - }; LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test with argument: {}", - null, arguments); - - MDC.put("mdc_01_retain", "retained"); - MDC.put("mdc_02_overwrite", "to_be_overwritten"); + null, new Object[0]); keyValuePairsLoggingEvent.setKeyValuePairs(List.of( new KeyValuePair("key_01_string", "value_01"), @@ -207,9 +199,7 @@ void shouldLogKeyValuePairs() { new KeyValuePair("", "value_05_empty_key"), new KeyValuePair(null, "value_06_null_key"), new KeyValuePair("key_07_boolean_true", true), - new KeyValuePair("key_08_boolean_false", false), - new KeyValuePair("mdc_02_overwrite", "overwritten_by_kvp"), - new KeyValuePair("structured_argument_02_overwrite", "overwritten_by_kvp") + new KeyValuePair("key_08_boolean_false", false) )); // WHEN @@ -218,14 +208,6 @@ void shouldLogKeyValuePairs() { // THEN assertThat(result) - // Arguments - .contains("Key Value Pairs Test with argument: argument_01") - .contains("\"structured_argument_01_retain\":\"retained\"") - // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for arguments - // MDC - .contains("\"mdc_01_retain\":\"retained\"") - // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for MDC - // Key Value Pairs .contains("\"key_01_string\":\"value_01\"") .contains("\"key_02_numeric\":2") .contains("\"key_03_decimal\":2.333") @@ -234,8 +216,6 @@ void shouldLogKeyValuePairs() { .contains("\"null\":\"value_06_null_key\"") .contains("\"key_07_boolean_true\":true") .contains("\"key_08_boolean_false\":false") - .contains("\"mdc_02_overwrite\":\"overwritten_by_kvp\"") - .contains("\"structured_argument_02_overwrite\":\"overwritten_by_kvp\"") ; } diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index 87aef30ca..3683cf106 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -450,16 +450,8 @@ void shouldLogKeyValuePairs() { LambdaJsonEncoder encoder = new LambdaJsonEncoder(); encoder.start(); - Object[] arguments = { - "argument_01", - StructuredArguments.entry("structured_argument_01_retain", "retained"), - StructuredArguments.entry("structured_argument_02_overwrite", "to_be_overwritten") - }; LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test with argument: {}", - null, arguments); - - MDC.put("mdc_01_retain", "retained"); - MDC.put("mdc_02_overwrite", "to_be_overwritten"); + null, new Object[0]); keyValuePairsLoggingEvent.setKeyValuePairs(List.of( new KeyValuePair("key_01_string", "value_01"), @@ -469,9 +461,7 @@ void shouldLogKeyValuePairs() { new KeyValuePair("", "value_05_empty_key"), new KeyValuePair(null, "value_06_null_key"), new KeyValuePair("key_07_boolean_true", true), - new KeyValuePair("key_08_boolean_false", false), - new KeyValuePair("mdc_02_overwrite", "overwritten_by_kvp"), - new KeyValuePair("structured_argument_02_overwrite", "overwritten_by_kvp") + new KeyValuePair("key_08_boolean_false", false) )); // WHEN @@ -480,14 +470,6 @@ void shouldLogKeyValuePairs() { // THEN assertThat(result) - // Arguments - .contains("Key Value Pairs Test with argument: argument_01") - .contains("\"structured_argument_01_retain\":\"retained\"") - // .doesNotContain("\"structured_argument_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for arguments - // MDC - .contains("\"mdc_01_retain\":\"retained\"") - // .doesNotContain("\"mdc_02_overwrite\":\"to_be_overwritten\"") // TODO: Deduplication not implemented for MDC - // Key Value Pairs .contains("\"key_01_string\":\"value_01\"") .contains("\"key_02_numeric\":2") .contains("\"key_03_decimal\":2.333") @@ -496,8 +478,6 @@ void shouldLogKeyValuePairs() { .contains("\"null\":\"value_06_null_key\"") .contains("\"key_07_boolean_true\":true") .contains("\"key_08_boolean_false\":false") - .contains("\"mdc_02_overwrite\":\"overwritten_by_kvp\"") - .contains("\"structured_argument_02_overwrite\":\"overwritten_by_kvp\"") ; } From e6e37864ad54a33058efdbff1b52f2b85d135377 Mon Sep 17 00:00:00 2001 From: Cornelius Matejka Date: Sun, 15 Feb 2026 09:30:59 +0100 Subject: [PATCH 9/9] feat(logging/logback): update docs, align logback with log4j implementation --- docs/core/logging.md | 14 +++++++++++--- .../powertools/logging/logback/JsonUtils.java | 6 ------ .../logging/logback/LambdaEcsEncoder.java | 15 +++++---------- .../logging/logback/LambdaJsonEncoder.java | 12 ++++-------- .../logging/internal/LambdaEcsEncoderTest.java | 4 ++-- .../logging/internal/LambdaJsonEncoderTest.java | 4 ++-- 6 files changed, 24 insertions(+), 31 deletions(-) diff --git a/docs/core/logging.md b/docs/core/logging.md index f950c81df..4a53e06e2 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -378,9 +378,17 @@ You can use Powertools for AWS Lambda Logging with either the `@Logging` annotat } ``` -???+ warning "SLF4J Fluent Logging API" - When using [SLF4J Fluent Logging API](https://www.slf4j.org/manual.html#fluent) **do not add reserved keys** listed in [standard structured keys](#standard-structured-keys) and [additional structured keys](#additional-structured-keys) as key-value pairs via `addKeyValue`. - This may cause unindented behavior. +???+ warning "Do not reuse reserved keys" + + Do not reuse reserved keys listed in [standard structured keys](#standard-structured-keys) and [additional structured keys](#additional-structured-keys). + This restriction applies to all structured logging mechanisms, including: + + - Log arguments + - MDC entries + - `addKeyValue(...)` or `addArgument(...)` of [SLF4J Fluent Logging API](https://www.slf4j.org/manual.html#fluent) + - Any other structured data fields + + Reusing reserved keys in any of these contexts may result in unintended or inconsistent behavior. ## Standard structured keys diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java index fbdec8e4a..67d6b268d 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/JsonUtils.java @@ -73,12 +73,6 @@ static void serializeMDCEntry(Map.Entry entry, JsonSerializer se } } - static void serializeKVPEntry(String key, Object value, JsonSerializer serializer) { - serializer.writeRaw(','); - serializer.writeFieldName(key); - serializer.writeObject(value); - } - static void serializeArguments(ILoggingEvent event, JsonSerializer serializer) throws IOException { Object[] arguments = event.getArgumentArray(); if (arguments != null) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java index 6eab1d7ec..b934f378d 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java @@ -36,7 +36,6 @@ import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.logging.internal.JsonSerializer; -import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; /** @@ -98,7 +97,8 @@ public byte[] headerBytes() { @SuppressWarnings("java:S106") @Override public byte[] encode(ILoggingEvent event) { - final Map mdcPropertyMap = event.getMDCPropertyMap(); + final Map mdcPropertyMap = new TreeMap<>(event.getMDCPropertyMap()); + mdcPropertyMap.putAll(getKeyValuePairs(event)); StringBuilder builder = new StringBuilder(); try (JsonSerializer serializer = new JsonSerializer(builder)) { @@ -131,8 +131,6 @@ public byte[] encode(ILoggingEvent event) { serializeMDCEntries(mdcPropertyMap, serializer); - serializeKeyValuePairs(event, serializer); - serializeArguments(event, serializer); serializer.writeEndObject(); @@ -143,14 +141,11 @@ public byte[] encode(ILoggingEvent event) { return builder.toString().getBytes(UTF_8); } - private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializer) { - Optional.ofNullable(event.getKeyValuePairs()) + private Map getKeyValuePairs(ILoggingEvent event) { + return Optional.ofNullable(event.getKeyValuePairs()) .orElse(Collections.emptyList()).stream() .filter(Objects::nonNull) - .map(kvp -> new AbstractMap.SimpleEntry<>(String.valueOf(kvp.key), kvp.value)) - .filter(kvp -> !PowertoolsLoggedFields.stringValues().contains(kvp.getKey())) - .sorted(Map.Entry.comparingByKey()) - .forEach(kvp -> serializeKVPEntry(kvp.getKey(), kvp.getValue(), serializer)); + .collect(TreeMap::new, (map, kvp) -> map.put(String.valueOf(kvp.key), String.valueOf(kvp.value)), TreeMap::putAll); } private void serializeFunctionInfo(JsonSerializer serializer, String arn, Map mdcPropertyMap) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java index 469fc2222..49368ae99 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java @@ -79,12 +79,11 @@ public byte[] encode(ILoggingEvent event) { serializeException(event, serializer); TreeMap sortedMap = new TreeMap<>(event.getMDCPropertyMap()); + sortedMap.putAll(getKeyValuePairs(event)); serializePowertools(sortedMap, serializer); serializeMDCEntries(sortedMap, serializer); - serializeKeyValuePairs(event, serializer); - serializeArguments(event, serializer); serializeThreadInfo(event, serializer); @@ -101,14 +100,11 @@ public byte[] encode(ILoggingEvent event) { return builder.toString().getBytes(UTF_8); } - private void serializeKeyValuePairs(ILoggingEvent event, JsonSerializer serializer) { - Optional.ofNullable(event.getKeyValuePairs()) + private Map getKeyValuePairs(ILoggingEvent event) { + return Optional.ofNullable(event.getKeyValuePairs()) .orElse(Collections.emptyList()).stream() .filter(Objects::nonNull) - .map(kvp -> new AbstractMap.SimpleEntry<>(String.valueOf(kvp.key), kvp.value)) - .filter(kvp -> !PowertoolsLoggedFields.stringValues().contains(kvp.getKey())) - .sorted(Map.Entry.comparingByKey()) - .forEach(kvp -> serializeKVPEntry(kvp.getKey(), kvp.getValue(), serializer)); + .collect(TreeMap::new, (map, kvp) -> map.put(String.valueOf(kvp.key), String.valueOf(kvp.value)), TreeMap::putAll); } private void serializeThreadInfo(ILoggingEvent event, JsonSerializer serializer) { diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index ac8cf8ace..2d5cb0a00 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -188,7 +188,7 @@ void shouldLogKeyValuePairs() { LambdaEcsEncoder encoder = new LambdaEcsEncoder(); encoder.start(); - LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test with argument: {}", + LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test", null, new Object[0]); keyValuePairsLoggingEvent.setKeyValuePairs(List.of( @@ -211,7 +211,7 @@ void shouldLogKeyValuePairs() { .contains("\"key_01_string\":\"value_01\"") .contains("\"key_02_numeric\":2") .contains("\"key_03_decimal\":2.333") - .contains("\"key_04_null\":null") + .contains("\"key_04_null\":\"null\"") .contains("\"\":\"value_05_empty_key\"") .contains("\"null\":\"value_06_null_key\"") .contains("\"key_07_boolean_true\":true") diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index 3683cf106..ca256ad5d 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -450,7 +450,7 @@ void shouldLogKeyValuePairs() { LambdaJsonEncoder encoder = new LambdaJsonEncoder(); encoder.start(); - LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test with argument: {}", + LoggingEvent keyValuePairsLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Key Value Pairs Test", null, new Object[0]); keyValuePairsLoggingEvent.setKeyValuePairs(List.of( @@ -473,7 +473,7 @@ void shouldLogKeyValuePairs() { .contains("\"key_01_string\":\"value_01\"") .contains("\"key_02_numeric\":2") .contains("\"key_03_decimal\":2.333") - .contains("\"key_04_null\":null") + .contains("\"key_04_null\":\"null\"") .contains("\"\":\"value_05_empty_key\"") .contains("\"null\":\"value_06_null_key\"") .contains("\"key_07_boolean_true\":true")