diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 774120b5a..e95a1fce2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,6 +12,7 @@ on:
- 'powertools-validation/**'
- 'powertools-parameters/**'
- 'powertools-metrics/**'
+ - 'powertools-test-suite/**'
- 'pom.xml'
- '.github/workflows/**'
push:
@@ -25,6 +26,7 @@ on:
- 'powertools-validation/**'
- 'powertools-parameters/**'
- 'powertools-metrics/**'
+ - 'powertools-test-suite/**'
- 'pom.xml'
- '.github/workflows/**'
jobs:
diff --git a/.github/workflows/spotbugs.yml b/.github/workflows/spotbugs.yml
index ff1a28c4d..be5f074fa 100644
--- a/.github/workflows/spotbugs.yml
+++ b/.github/workflows/spotbugs.yml
@@ -12,6 +12,7 @@ on:
- 'powertools-validation/**'
- 'powertools-parameters/**'
- 'powertools-metrics/**'
+ - 'powertools-test-suite/**'
- 'pom.xml'
- '.github/workflows/**'
jobs:
diff --git a/pom.xml b/pom.xml
index 701a0af02..a3ee3423a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
powertools-metrics
powertools-parameters
powertools-validation
+ powertools-test-suite
@@ -88,6 +89,21 @@
powertools-core
${project.version}
+
+ software.amazon.lambda
+ powertools-logging
+ ${project.version}
+
+
+ software.amazon.lambda
+ powertools-sqs
+ ${project.version}
+
+
+ software.amazon.lambda
+ powertools-tracing
+ ${project.version}
+
com.amazonaws
aws-lambda-java-core
diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java
index 8f37377e1..34f3bf312 100644
--- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java
+++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java
@@ -37,6 +37,7 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import software.amazon.lambda.powertools.logging.Logging;
import software.amazon.lambda.powertools.logging.LoggingUtils;
@@ -57,6 +58,7 @@
import static software.amazon.lambda.powertools.logging.LoggingUtils.objectMapper;
@Aspect
+@DeclarePrecedence("*, SqsLargeMessageAspect, LambdaLoggingAspect")
public final class LambdaLoggingAspect {
private static final Logger LOG = LogManager.getLogger(LambdaLoggingAspect.class);
private static final Random SAMPLER = new Random();
diff --git a/powertools-test-suite/pom.xml b/powertools-test-suite/pom.xml
new file mode 100644
index 000000000..af2907c5d
--- /dev/null
+++ b/powertools-test-suite/pom.xml
@@ -0,0 +1,151 @@
+
+
+ 4.0.0
+
+ powertools-test-suite
+ jar
+
+
+ powertools-parent
+ software.amazon.lambda
+ 1.7.3
+
+
+ AWS Lambda Powertools Java library Test Suite
+
+ A suite of tests for interactions between the various Powertools modules.
+
+ https://aws.amazon.com/lambda/
+
+ GitHub Issues
+ https://github.com/awslabs/aws-lambda-powertools-java/issues
+
+
+ https://github.com/awslabs/aws-lambda-powertools-java.git
+
+
+
+ AWS Lambda Powertools team
+ Amazon Web Services
+ https://aws.amazon.com/
+
+
+
+
+ true
+
+
+
+
+ ossrh
+ https://aws.oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+
+ software.amazon.lambda
+ powertools-core
+
+
+ org.apache.logging.log4j
+ log4j-jcl
+ 2.14.1
+
+
+ com.amazonaws
+ aws-lambda-java-core
+
+
+ com.amazonaws
+ aws-lambda-java-events
+
+
+ software.amazon.lambda
+ powertools-logging
+
+
+ software.amazon.lambda
+ powertools-tracing
+
+
+ software.amazon.lambda
+ powertools-sqs
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.apache.commons
+ commons-lang3
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.aspectj
+ aspectjweaver
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.skyscreamer
+ jsonassert
+ test
+
+
+
+
+
+
+ org.codehaus.mojo
+ aspectj-maven-plugin
+ 1.14.0
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ ${maven.compiler.target}
+
+
+ software.amazon.lambda
+ powertools-logging
+
+
+ software.amazon.lambda
+ powertools-tracing
+
+
+ software.amazon.lambda
+ powertools-sqs
+
+
+
+
+
+
+ compile
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/LoggingOrderTest.java b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/LoggingOrderTest.java
new file mode 100644
index 000000000..285e7d2fa
--- /dev/null
+++ b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/LoggingOrderTest.java
@@ -0,0 +1,168 @@
+package software.amazon.lambda.powertools.testsuite;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Map;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.events.SQSEvent;
+import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.model.S3Object;
+import com.amazonaws.xray.AWSXRay;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.ThreadContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor;
+import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect;
+import software.amazon.lambda.powertools.sqs.internal.SqsLargeMessageAspect;
+import software.amazon.lambda.powertools.testsuite.handler.LoggingOrderMessageHandler;
+import software.amazon.lambda.powertools.testsuite.handler.TracingLoggingStreamMessageHandler;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.openMocks;
+
+public class LoggingOrderTest {
+
+ private static final String BUCKET_NAME = "ms-extended-sqs-client";
+ private static final String BUCKET_KEY = "c71eb2ae-37e0-4265-8909-32f4153faddf";
+
+ @Mock
+ private Context context;
+
+ @Mock
+ private AmazonS3 amazonS3;
+
+ @BeforeEach
+ void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException {
+ openMocks(this);
+ writeStaticField(SqsLargeMessageAspect.class, "amazonS3", amazonS3, true);
+ ThreadContext.clearAll();
+ writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true);
+ setupContext();
+ //Make sure file is cleaned up before running full stack logging regression
+ FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close();
+ resetLogLevel(Level.INFO);
+ AWSXRay.beginSegment(LoggingOrderTest.class.getName());
+ }
+
+ @AfterEach
+ void tearDown() {
+ AWSXRay.endSegment();
+ }
+
+ /**
+ * The SQSEvent payload will be altered by the @SqsLargeMessage annotation. Logging of the event should happen
+ * after the event has been altered
+ */
+ @Test
+ public void testThatLoggingAnnotationActsLast() throws IOException {
+ S3Object s3Response = new S3Object();
+ s3Response.setObjectContent(new ByteArrayInputStream("A big message".getBytes()));
+
+ when(amazonS3.getObject(BUCKET_NAME, BUCKET_KEY)).thenReturn(s3Response);
+ SQSEvent sqsEvent = messageWithBody("[\"software.amazon.payloadoffloading.PayloadS3Pointer\",{\"s3BucketName\":\"" + BUCKET_NAME + "\",\"s3Key\":\"" + BUCKET_KEY + "\"}]");
+
+ LoggingOrderMessageHandler requestHandler = new LoggingOrderMessageHandler();
+ requestHandler.handleRequest(sqsEvent, context);
+
+ assertThat(Files.lines(Paths.get("target/logfile.json")))
+ .hasSize(2)
+ .satisfies(line -> {
+ Map actual = parseToMap(line.get(0));
+
+ String message = actual.get("message").toString();
+
+ assertThat(message)
+ .contains("A big message");
+ });
+ }
+
+ @Test
+ public void testLoggingAnnotationActsAfterTracingForStreamingHandler() throws IOException {
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ S3EventNotification s3EventNotification = s3EventNotification();
+
+ TracingLoggingStreamMessageHandler handler = new TracingLoggingStreamMessageHandler();
+ handler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(s3EventNotification)), output, context);
+
+ assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8))
+ .isNotEmpty();
+ }
+
+ private void setupContext() {
+ when(context.getFunctionName()).thenReturn("testFunction");
+ when(context.getInvokedFunctionArn()).thenReturn("testArn");
+ when(context.getFunctionVersion()).thenReturn("1");
+ when(context.getMemoryLimitInMB()).thenReturn(10);
+ when(context.getAwsRequestId()).thenReturn("RequestId");
+ }
+
+ private void resetLogLevel(Level level) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class);
+ resetLogLevels.setAccessible(true);
+ resetLogLevels.invoke(null, level);
+ writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true);
+ }
+
+ private Map parseToMap(String stringAsJson) {
+ try {
+ return new ObjectMapper().readValue(stringAsJson, Map.class);
+ } catch (JsonProcessingException e) {
+ fail("Failed parsing logger line " + stringAsJson);
+ return emptyMap();
+ }
+ }
+
+ private S3EventNotification s3EventNotification() {
+ S3EventNotification.S3EventNotificationRecord record = new S3EventNotification.S3EventNotificationRecord("us-west-2",
+ "ObjectCreated:Put",
+ "aws:s3",
+ null,
+ "2.1",
+ new S3EventNotification.RequestParametersEntity("127.0.0.1"),
+ new S3EventNotification.ResponseElementsEntity("C3D13FE58DE4C810", "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"),
+ new S3EventNotification.S3Entity("testConfigRule",
+ new S3EventNotification.S3BucketEntity("mybucket",
+ new S3EventNotification.UserIdentityEntity("A3NL1KOZZKExample"),
+ "arn:aws:s3:::mybucket"),
+ new S3EventNotification.S3ObjectEntity("HappyFace.jpg",
+ 1024L,
+ "d41d8cd98f00b204e9800998ecf8427e",
+ "096fKKXTRTtl3on89fVO.nfljtsv6qko",
+ "0055AED6DCD90281E5"),
+ "1.0"),
+ new S3EventNotification.UserIdentityEntity("AIDAJDPLRKLG7UEXAMPLE")
+ );
+
+ return new S3EventNotification(singletonList(record));
+ }
+
+ private SQSEvent messageWithBody(String messageBody) {
+ SQSEvent.SQSMessage sqsMessage = new SQSEvent.SQSMessage();
+ sqsMessage.setBody(messageBody);
+ SQSEvent sqsEvent = new SQSEvent();
+ sqsEvent.setRecords(singletonList(sqsMessage));
+ return sqsEvent;
+ }
+}
\ No newline at end of file
diff --git a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/LoggingOrderMessageHandler.java b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/LoggingOrderMessageHandler.java
new file mode 100644
index 000000000..a85c81b1d
--- /dev/null
+++ b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/LoggingOrderMessageHandler.java
@@ -0,0 +1,17 @@
+package software.amazon.lambda.powertools.testsuite.handler;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+import com.amazonaws.services.lambda.runtime.events.SQSEvent;
+import software.amazon.lambda.powertools.logging.Logging;
+import software.amazon.lambda.powertools.sqs.SqsLargeMessage;
+
+public class LoggingOrderMessageHandler implements RequestHandler {
+
+ @Override
+ @SqsLargeMessage
+ @Logging(logEvent = true)
+ public String handleRequest(SQSEvent sqsEvent, Context context) {
+ return sqsEvent.getRecords().get(0).getBody();
+ }
+}
diff --git a/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/TracingLoggingStreamMessageHandler.java b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/TracingLoggingStreamMessageHandler.java
new file mode 100644
index 000000000..d0f2b3ac5
--- /dev/null
+++ b/powertools-test-suite/src/test/java/software/amazon/lambda/powertools/testsuite/handler/TracingLoggingStreamMessageHandler.java
@@ -0,0 +1,23 @@
+package software.amazon.lambda.powertools.testsuite.handler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import software.amazon.lambda.powertools.logging.Logging;
+import software.amazon.lambda.powertools.tracing.Tracing;
+
+public class TracingLoggingStreamMessageHandler implements RequestStreamHandler {
+
+ @Logging(logEvent = true)
+ @Tracing
+ @Override
+ public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(output, mapper.readValue(input, Map.class));
+ }
+}
diff --git a/powertools-test-suite/src/test/resources/log4j2.xml b/powertools-test-suite/src/test/resources/log4j2.xml
new file mode 100644
index 000000000..8ac9b34ce
--- /dev/null
+++ b/powertools-test-suite/src/test/resources/log4j2.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file