diff --git a/pom.xml b/pom.xml
index 1e4c3a9fd..9e38de657 100644
--- a/pom.xml
+++ b/pom.xml
@@ -501,6 +501,11 @@
org.apache.maven.plugins
maven-surefire-plugin
+
+
+ on-demand
+
+
org.jacoco
@@ -632,6 +637,9 @@
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
+
+ on-demand
+
diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java
index 69fc1283a..4c4e8e9db 100644
--- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java
+++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java
@@ -28,4 +28,6 @@ private LambdaConstants() {
public static final String ROOT_EQUALS = "Root=";
public static final String POWERTOOLS_SERVICE_NAME = "POWERTOOLS_SERVICE_NAME";
public static final String SERVICE_UNDEFINED = "service_undefined";
+ public static final String AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE";
+ public static final String ON_DEMAND_INVOCATION_TYPE = "on-demand";
}
diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java
index 393835d1e..15bff15d6 100644
--- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java
+++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java
@@ -93,7 +93,14 @@ protected static void resetServiceName() {
}
public static boolean isColdStart() {
- return isColdStart == null;
+ if (isColdStart != null) {
+ return isColdStart;
+ }
+
+ String initType = System.getenv(LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE);
+ isColdStart = LambdaConstants.ON_DEMAND_INVOCATION_TYPE.equals(initType);
+
+ return isColdStart;
}
public static void coldStartDone() {
diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java
index 5c6bdc020..0726a9e77 100644
--- a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java
+++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java
@@ -216,6 +216,7 @@ void extractContext_notKnownHandler() {
}
@Test
+ @SetEnvironmentVariable(key = LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE, value = LambdaConstants.ON_DEMAND_INVOCATION_TYPE)
void isColdStart() {
boolean isColdStart = LambdaHandlerProcessor.isColdStart();
diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml b/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml
index a9cf5739b..33298731b 100644
--- a/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml
+++ b/powertools-idempotency/powertools-idempotency-dynamodb/pom.xml
@@ -104,7 +104,6 @@
test
-
generate-graalvm-files
diff --git a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java
index b5c816286..26e5ec5d9 100644
--- a/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java
+++ b/powertools-idempotency/powertools-idempotency-dynamodb/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/dynamodb/DynamoDBPersistenceStoreTest.java
@@ -47,342 +47,358 @@
import software.amazon.lambda.powertools.idempotency.persistence.DataRecord;
/**
- * These test are using DynamoDBLocal and sqlite, see https://nickolasfisher.com/blog/Configuring-an-In-Memory-DynamoDB-instance-with-Java-for-Integration-Testing
+ * These test are using DynamoDBLocal and sqlite, see
+ * https://www.nickolasfisher.com/blog/configuring-an-in-memory-dynamodb-instance-with-java-for-integration-testing/
* NOTE: on a Mac with Apple Chipset, you need to use the Oracle JDK x86 64-bit
*/
class DynamoDBPersistenceStoreTest extends DynamoDBConfig {
- protected static final String TABLE_NAME_CUSTOM = "idempotency_table_custom";
- private Map key;
- private DynamoDBPersistenceStore dynamoDBPersistenceStore;
-
- @Test
- void putRecord_shouldCreateRecordInDynamoDB() throws IdempotencyItemAlreadyExistsException {
- Instant now = Instant.now();
- long expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
- dynamoDBPersistenceStore.putRecord(new DataRecord("key", DataRecord.Status.COMPLETED, expiry, null, null), now);
-
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
- Map item = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
- assertThat(item).isNotNull();
- assertThat(item.get("status").s()).isEqualTo("COMPLETED");
- assertThat(item.get("expiration").n()).isEqualTo(String.valueOf(expiry));
- }
-
- @Test
- void putRecord_shouldCreateRecordInDynamoDB_IfPreviousExpired() {
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
-
- // GIVEN: Insert a fake item with same id and expired
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.minus(30, ChronoUnit.SECONDS).getEpochSecond();
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
- item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build());
- item.put("data", AttributeValue.builder().s("Fake Data").build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
-
- // WHEN: call putRecord
- long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
- dynamoDBPersistenceStore.putRecord(
- new DataRecord("key",
- DataRecord.Status.INPROGRESS,
- expiry2,
- null,
- null),
- now);
-
- // THEN: an item is inserted
- Map itemInDb = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
- assertThat(itemInDb).isNotNull();
- assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS");
- assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry2));
- }
-
- @Test
- void putRecord_shouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() {
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
-
- // GIVEN: Insert a fake item with same id and progress expired (Lambda timed out before and we allow a new
- // execution)
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond();
- long progressExpiry = now.minus(30, ChronoUnit.SECONDS).toEpochMilli();
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
- item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
- item.put("data", AttributeValue.builder().s("Fake Data").build());
- item.put("in_progress_expiration", AttributeValue.builder().n(String.valueOf(progressExpiry)).build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
-
- // WHEN: call putRecord
- long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
- dynamoDBPersistenceStore.putRecord(
- new DataRecord("key",
- DataRecord.Status.INPROGRESS,
- expiry2,
- null,
- null),
- now);
-
- // THEN: an item is inserted
- Map itemInDb = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
- assertThat(itemInDb).isNotNull();
- assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS");
- assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry2));
- }
-
- @Test
- void putRecord_shouldThrowIdempotencyItemAlreadyExistsException_IfRecordAlreadyExist() {
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
-
- // GIVEN: Insert a fake item with same id
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond();
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); // not expired
- item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build());
- item.put("data", AttributeValue.builder().s("Fake Data").build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
-
- // WHEN: call putRecord
- long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
- DataRecord recordToInsert = new DataRecord("key",
- DataRecord.Status.INPROGRESS,
- expiry2,
- null,
- null);
- assertThatThrownBy(() -> dynamoDBPersistenceStore.putRecord(recordToInsert, now))
- .isInstanceOf(IdempotencyItemAlreadyExistsException.class)
- // DataRecord should be present due to returnValuesOnConditionCheckFailure("ALL_OLD")
- .matches(e -> ((IdempotencyItemAlreadyExistsException) e).getDataRecord().isPresent());
-
- // THEN: item was not updated, retrieve the initial one
- Map itemInDb = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
- assertThat(itemInDb).isNotNull();
- assertThat(itemInDb.get("status").s()).isEqualTo("COMPLETED");
- assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry));
- assertThat(itemInDb.get("data").s()).isEqualTo("Fake Data");
- }
-
- @Test
- void putRecord_shouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() {
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
-
- // GIVEN: Insert a fake item with same id
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond(); // not expired
- long progressExpiry = now.plus(30, ChronoUnit.SECONDS).toEpochMilli(); // not expired
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
- item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
- item.put("data", AttributeValue.builder().s("Fake Data").build());
- item.put("in_progress_expiration", AttributeValue.builder().n(String.valueOf(progressExpiry)).build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
-
- // WHEN: call putRecord
- long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
- DataRecord recordToInsert = new DataRecord("key",
- DataRecord.Status.INPROGRESS,
- expiry2,
- "Fake Data 2",
- null);
- assertThatThrownBy(() -> dynamoDBPersistenceStore.putRecord(recordToInsert, now))
- .isInstanceOf(IdempotencyItemAlreadyExistsException.class)
- // DataRecord should be present due to returnValuesOnConditionCheckFailure("ALL_OLD")
- .matches(e -> ((IdempotencyItemAlreadyExistsException) e).getDataRecord().isPresent());
-
- // THEN: item was not updated, retrieve the initial one
- Map itemInDb = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
- assertThat(itemInDb).isNotNull();
- assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS");
- assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry));
- assertThat(itemInDb.get("data").s()).isEqualTo("Fake Data");
- }
-
- @Test
- void getRecord_shouldReturnExistingRecord() throws IdempotencyItemNotFoundException {
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
-
- // GIVEN: Insert a fake item with same id
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond();
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
- item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build());
- item.put("data", AttributeValue.builder().s("Fake Data").build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
-
- // WHEN
- DataRecord dr = dynamoDBPersistenceStore.getRecord("key");
-
- // THEN
- assertThat(dr.getIdempotencyKey()).isEqualTo("key");
- assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.COMPLETED);
- assertThat(dr.getResponseData()).isEqualTo("Fake Data");
- assertThat(dr.getExpiryTimestamp()).isEqualTo(expiry);
- }
-
- @Test
- void getRecord_shouldThrowException_whenRecordIsAbsent() {
- assertThatThrownBy(() -> dynamoDBPersistenceStore.getRecord("key"))
- .isInstanceOf(IdempotencyItemNotFoundException.class);
- }
-
- @Test
- void updateRecord_shouldUpdateRecord() {
- // GIVEN: Insert a fake item with same id
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.plus(360, ChronoUnit.SECONDS).getEpochSecond();
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
- item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
- // enable payload validation
- dynamoDBPersistenceStore.configure(IdempotencyConfig.builder().withPayloadValidationJMESPath("path").build(),
- null);
-
- // WHEN
- expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
- DataRecord dr = new DataRecord("key", DataRecord.Status.COMPLETED, expiry, "Fake result", "hash");
- dynamoDBPersistenceStore.updateRecord(dr);
-
- // THEN
- Map itemInDb = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
- assertThat(itemInDb.get("status").s()).isEqualTo("COMPLETED");
- assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry));
- assertThat(itemInDb.get("data").s()).isEqualTo("Fake result");
- assertThat(itemInDb.get("validation").s()).isEqualTo("hash");
- }
-
- @Test
- void deleteRecord_shouldDeleteRecord() {
- // GIVEN: Insert a fake item with same id
- key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
- Map item = new HashMap<>(key);
- Instant now = Instant.now();
- long expiry = now.plus(360, ChronoUnit.SECONDS).getEpochSecond();
- item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
- item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
- client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
- assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isEqualTo(1);
-
- // WHEN
- dynamoDBPersistenceStore.deleteRecord("key");
-
- // THEN
- assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isZero();
- }
-
- @Test
- void endToEndWithCustomAttrNamesAndSortKey() throws IdempotencyItemNotFoundException {
- try {
- client.createTable(CreateTableRequest.builder()
- .tableName(TABLE_NAME_CUSTOM)
- .keySchema(
- KeySchemaElement.builder().keyType(KeyType.HASH).attributeName("key").build(),
- KeySchemaElement.builder().keyType(KeyType.RANGE).attributeName("sortkey").build())
- .attributeDefinitions(
- AttributeDefinition.builder().attributeName("key").attributeType(ScalarAttributeType.S)
- .build(),
- AttributeDefinition.builder().attributeName("sortkey").attributeType(ScalarAttributeType.S)
- .build())
- .billingMode(BillingMode.PAY_PER_REQUEST)
- .build());
-
- DynamoDBPersistenceStore persistenceStore = DynamoDBPersistenceStore.builder()
- .withTableName(TABLE_NAME_CUSTOM)
- .withDynamoDbClient(client)
- .withDataAttr("result")
- .withExpiryAttr("expiry")
- .withKeyAttr("key")
- .withSortKeyAttr("sortkey")
- .withStaticPkValue("pk")
- .withStatusAttr("state")
- .withValidationAttr("valid")
- .build();
-
- Instant now = Instant.now();
- DataRecord dr = new DataRecord(
- "mykey",
- DataRecord.Status.INPROGRESS,
- now.plus(400, ChronoUnit.SECONDS).getEpochSecond(),
- null,
- null);
- // PUT
- persistenceStore.putRecord(dr, now);
-
- Map customKey = new HashMap<>();
- customKey.put("key", AttributeValue.builder().s("pk").build());
- customKey.put("sortkey", AttributeValue.builder().s("mykey").build());
-
- Map itemInDb = client
- .getItem(GetItemRequest.builder().tableName(TABLE_NAME_CUSTOM).key(customKey).build()).item();
-
- // GET
- DataRecord recordInDb = persistenceStore.getRecord("mykey");
-
- assertThat(itemInDb).isNotNull();
- assertThat(itemInDb.get("key").s()).isEqualTo("pk");
- assertThat(itemInDb.get("sortkey").s()).isEqualTo(recordInDb.getIdempotencyKey());
- assertThat(itemInDb.get("state").s()).isEqualTo(recordInDb.getStatus().toString());
- assertThat(itemInDb.get("expiry").n()).isEqualTo(String.valueOf(recordInDb.getExpiryTimestamp()));
-
- // UPDATE
- DataRecord updatedRecord = new DataRecord(
- "mykey",
- DataRecord.Status.COMPLETED,
- now.plus(500, ChronoUnit.SECONDS).getEpochSecond(),
- "response",
- null);
- persistenceStore.updateRecord(updatedRecord);
- recordInDb = persistenceStore.getRecord("mykey");
- assertThat(recordInDb).isEqualTo(updatedRecord);
-
- // DELETE
- persistenceStore.deleteRecord("mykey");
- assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME_CUSTOM).build()).count()).isEqualTo(0);
-
- } finally {
- try {
- client.deleteTable(DeleteTableRequest.builder().tableName(TABLE_NAME_CUSTOM).build());
- } catch (Exception e) {
- // OK
- }
+ protected static final String TABLE_NAME_CUSTOM = "idempotency_table_custom";
+ private Map key;
+ private DynamoDBPersistenceStore dynamoDBPersistenceStore;
+
+ @Test
+ void putRecord_shouldCreateRecordInDynamoDB() throws IdempotencyItemAlreadyExistsException {
+ Instant now = Instant.now();
+ long expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
+ dynamoDBPersistenceStore
+ .putRecord(new DataRecord("key", DataRecord.Status.COMPLETED, expiry, null, null), now);
+
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+ Map item = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
+ assertThat(item).isNotNull();
+ assertThat(item.get("status").s()).isEqualTo("COMPLETED");
+ assertThat(item.get("expiration").n()).isEqualTo(String.valueOf(expiry));
+ }
+
+ @Test
+ void putRecord_shouldCreateRecordInDynamoDB_IfPreviousExpired() {
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+
+ // GIVEN: Insert a fake item with same id and expired
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.minus(30, ChronoUnit.SECONDS).getEpochSecond();
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build());
+ item.put("data", AttributeValue.builder().s("Fake Data").build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+
+ // WHEN: call putRecord
+ long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
+ dynamoDBPersistenceStore.putRecord(
+ new DataRecord("key",
+ DataRecord.Status.INPROGRESS,
+ expiry2,
+ null,
+ null),
+ now);
+
+ // THEN: an item is inserted
+ Map itemInDb = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
+ assertThat(itemInDb).isNotNull();
+ assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS");
+ assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry2));
+ }
+
+ @Test
+ void putRecord_shouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() {
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+
+ // GIVEN: Insert a fake item with same id and progress expired (Lambda timed out
+ // before and we allow a new
+ // execution)
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond();
+ long progressExpiry = now.minus(30, ChronoUnit.SECONDS).toEpochMilli();
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
+ item.put("data", AttributeValue.builder().s("Fake Data").build());
+ item.put("in_progress_expiration", AttributeValue.builder().n(String.valueOf(progressExpiry)).build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+
+ // WHEN: call putRecord
+ long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
+ dynamoDBPersistenceStore.putRecord(
+ new DataRecord("key",
+ DataRecord.Status.INPROGRESS,
+ expiry2,
+ null,
+ null),
+ now);
+
+ // THEN: an item is inserted
+ Map itemInDb = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
+ assertThat(itemInDb).isNotNull();
+ assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS");
+ assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry2));
+ }
+
+ @Test
+ void putRecord_shouldThrowIdempotencyItemAlreadyExistsException_IfRecordAlreadyExist() {
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+
+ // GIVEN: Insert a fake item with same id
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond();
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build()); // not expired
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build());
+ item.put("data", AttributeValue.builder().s("Fake Data").build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+
+ // WHEN: call putRecord
+ long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
+ DataRecord recordToInsert = new DataRecord("key",
+ DataRecord.Status.INPROGRESS,
+ expiry2,
+ null,
+ null);
+ assertThatThrownBy(() -> dynamoDBPersistenceStore.putRecord(recordToInsert, now))
+ .isInstanceOf(IdempotencyItemAlreadyExistsException.class)
+ // DataRecord should be present due to
+ // returnValuesOnConditionCheckFailure("ALL_OLD")
+ .matches(e -> ((IdempotencyItemAlreadyExistsException) e).getDataRecord().isPresent());
+
+ // THEN: item was not updated, retrieve the initial one
+ Map itemInDb = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
+ assertThat(itemInDb).isNotNull();
+ assertThat(itemInDb.get("status").s()).isEqualTo("COMPLETED");
+ assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry));
+ assertThat(itemInDb.get("data").s()).isEqualTo("Fake Data");
+ }
+
+ @Test
+ void putRecord_shouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() {
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+
+ // GIVEN: Insert a fake item with same id
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond(); // not expired
+ long progressExpiry = now.plus(30, ChronoUnit.SECONDS).toEpochMilli(); // not expired
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
+ item.put("data", AttributeValue.builder().s("Fake Data").build());
+ item.put("in_progress_expiration", AttributeValue.builder().n(String.valueOf(progressExpiry)).build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+
+ // WHEN: call putRecord
+ long expiry2 = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
+ DataRecord recordToInsert = new DataRecord("key",
+ DataRecord.Status.INPROGRESS,
+ expiry2,
+ "Fake Data 2",
+ null);
+ assertThatThrownBy(() -> dynamoDBPersistenceStore.putRecord(recordToInsert, now))
+ .isInstanceOf(IdempotencyItemAlreadyExistsException.class)
+ // DataRecord should be present due to
+ // returnValuesOnConditionCheckFailure("ALL_OLD")
+ .matches(e -> ((IdempotencyItemAlreadyExistsException) e).getDataRecord().isPresent());
+
+ // THEN: item was not updated, retrieve the initial one
+ Map itemInDb = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
+ assertThat(itemInDb).isNotNull();
+ assertThat(itemInDb.get("status").s()).isEqualTo("INPROGRESS");
+ assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry));
+ assertThat(itemInDb.get("data").s()).isEqualTo("Fake Data");
+ }
+
+ @Test
+ void getRecord_shouldReturnExistingRecord() throws IdempotencyItemNotFoundException {
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+
+ // GIVEN: Insert a fake item with same id
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.plus(30, ChronoUnit.SECONDS).getEpochSecond();
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.COMPLETED.toString()).build());
+ item.put("data", AttributeValue.builder().s("Fake Data").build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+
+ // WHEN
+ DataRecord dr = dynamoDBPersistenceStore.getRecord("key");
+
+ // THEN
+ assertThat(dr.getIdempotencyKey()).isEqualTo("key");
+ assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.COMPLETED);
+ assertThat(dr.getResponseData()).isEqualTo("Fake Data");
+ assertThat(dr.getExpiryTimestamp()).isEqualTo(expiry);
+ }
+
+ @Test
+ void getRecord_shouldThrowException_whenRecordIsAbsent() {
+ assertThatThrownBy(() -> dynamoDBPersistenceStore.getRecord("key"))
+ .isInstanceOf(IdempotencyItemNotFoundException.class);
+ }
+
+ @Test
+ void updateRecord_shouldUpdateRecord() {
+ // GIVEN: Insert a fake item with same id
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.plus(360, ChronoUnit.SECONDS).getEpochSecond();
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+ // enable payload validation
+ dynamoDBPersistenceStore.configure(
+ IdempotencyConfig.builder().withPayloadValidationJMESPath("path").build(),
+ null);
+
+ // WHEN
+ expiry = now.plus(3600, ChronoUnit.SECONDS).getEpochSecond();
+ DataRecord dr = new DataRecord("key", DataRecord.Status.COMPLETED, expiry, "Fake result", "hash");
+ dynamoDBPersistenceStore.updateRecord(dr);
+
+ // THEN
+ Map itemInDb = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME).key(key).build()).item();
+ assertThat(itemInDb.get("status").s()).isEqualTo("COMPLETED");
+ assertThat(itemInDb.get("expiration").n()).isEqualTo(String.valueOf(expiry));
+ assertThat(itemInDb.get("data").s()).isEqualTo("Fake result");
+ assertThat(itemInDb.get("validation").s()).isEqualTo("hash");
+ }
+
+ @Test
+ void deleteRecord_shouldDeleteRecord() {
+ // GIVEN: Insert a fake item with same id
+ key = Collections.singletonMap("id", AttributeValue.builder().s("key").build());
+ Map item = new HashMap<>(key);
+ Instant now = Instant.now();
+ long expiry = now.plus(360, ChronoUnit.SECONDS).getEpochSecond();
+ item.put("expiration", AttributeValue.builder().n(String.valueOf(expiry)).build());
+ item.put("status", AttributeValue.builder().s(DataRecord.Status.INPROGRESS.toString()).build());
+ client.putItem(PutItemRequest.builder().tableName(TABLE_NAME).item(item).build());
+ assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isEqualTo(1);
+
+ // WHEN
+ dynamoDBPersistenceStore.deleteRecord("key");
+
+ // THEN
+ assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME).build()).count()).isZero();
+ }
+
+ @Test
+ void endToEndWithCustomAttrNamesAndSortKey() throws IdempotencyItemNotFoundException {
+ try {
+ client.createTable(CreateTableRequest.builder()
+ .tableName(TABLE_NAME_CUSTOM)
+ .keySchema(
+ KeySchemaElement.builder().keyType(KeyType.HASH)
+ .attributeName("key").build(),
+ KeySchemaElement.builder().keyType(KeyType.RANGE)
+ .attributeName("sortkey").build())
+ .attributeDefinitions(
+ AttributeDefinition.builder().attributeName("key")
+ .attributeType(ScalarAttributeType.S)
+ .build(),
+ AttributeDefinition.builder().attributeName("sortkey")
+ .attributeType(ScalarAttributeType.S)
+ .build())
+ .billingMode(BillingMode.PAY_PER_REQUEST)
+ .build());
+
+ DynamoDBPersistenceStore persistenceStore = DynamoDBPersistenceStore.builder()
+ .withTableName(TABLE_NAME_CUSTOM)
+ .withDynamoDbClient(client)
+ .withDataAttr("result")
+ .withExpiryAttr("expiry")
+ .withKeyAttr("key")
+ .withSortKeyAttr("sortkey")
+ .withStaticPkValue("pk")
+ .withStatusAttr("state")
+ .withValidationAttr("valid")
+ .build();
+
+ Instant now = Instant.now();
+ DataRecord dr = new DataRecord(
+ "mykey",
+ DataRecord.Status.INPROGRESS,
+ now.plus(400, ChronoUnit.SECONDS).getEpochSecond(),
+ null,
+ null);
+ // PUT
+ persistenceStore.putRecord(dr, now);
+
+ Map customKey = new HashMap<>();
+ customKey.put("key", AttributeValue.builder().s("pk").build());
+ customKey.put("sortkey", AttributeValue.builder().s("mykey").build());
+
+ Map itemInDb = client
+ .getItem(GetItemRequest.builder().tableName(TABLE_NAME_CUSTOM).key(customKey)
+ .build())
+ .item();
+
+ // GET
+ DataRecord recordInDb = persistenceStore.getRecord("mykey");
+
+ assertThat(itemInDb).isNotNull();
+ assertThat(itemInDb.get("key").s()).isEqualTo("pk");
+ assertThat(itemInDb.get("sortkey").s()).isEqualTo(recordInDb.getIdempotencyKey());
+ assertThat(itemInDb.get("state").s()).isEqualTo(recordInDb.getStatus().toString());
+ assertThat(itemInDb.get("expiry").n())
+ .isEqualTo(String.valueOf(recordInDb.getExpiryTimestamp()));
+
+ // UPDATE
+ DataRecord updatedRecord = new DataRecord(
+ "mykey",
+ DataRecord.Status.COMPLETED,
+ now.plus(500, ChronoUnit.SECONDS).getEpochSecond(),
+ "response",
+ null);
+ persistenceStore.updateRecord(updatedRecord);
+ recordInDb = persistenceStore.getRecord("mykey");
+ assertThat(recordInDb).isEqualTo(updatedRecord);
+
+ // DELETE
+ persistenceStore.deleteRecord("mykey");
+ assertThat(client.scan(ScanRequest.builder().tableName(TABLE_NAME_CUSTOM).build()).count())
+ .isEqualTo(0);
+
+ } finally {
+ try {
+ client.deleteTable(DeleteTableRequest.builder().tableName(TABLE_NAME_CUSTOM).build());
+ } catch (Exception e) {
+ // OK
+ }
+ }
+ }
+
+ @Test
+ @SetEnvironmentVariable(key = Constants.IDEMPOTENCY_DISABLED_ENV, value = "true")
+ void idempotencyDisabled_noClientShouldBeCreated() {
+ DynamoDBPersistenceStore store = DynamoDBPersistenceStore.builder().withTableName(TABLE_NAME).build();
+ assertThatThrownBy(() -> store.getRecord("fake"))
+ .isInstanceOf(NullPointerException.class);
+ }
+
+ @BeforeEach
+ void setup() {
+ dynamoDBPersistenceStore = DynamoDBPersistenceStore.builder()
+ .withTableName(TABLE_NAME)
+ .withDynamoDbClient(client)
+ .build();
+ }
+
+ @AfterEach
+ void emptyDB() {
+ // Clear all items from the table
+ client.scan(ScanRequest.builder().tableName(TABLE_NAME).build())
+ .items()
+ .forEach(item -> {
+ Map itemKey = Collections.singletonMap("id",
+ item.get("id"));
+ client.deleteItem(DeleteItemRequest.builder().tableName(TABLE_NAME).key(itemKey)
+ .build());
+ });
+ key = null;
}
- }
-
- @Test
- @SetEnvironmentVariable(key = Constants.IDEMPOTENCY_DISABLED_ENV, value = "true")
- void idempotencyDisabled_noClientShouldBeCreated() {
- DynamoDBPersistenceStore store = DynamoDBPersistenceStore.builder().withTableName(TABLE_NAME).build();
- assertThatThrownBy(() -> store.getRecord("fake"))
- .isInstanceOf(NullPointerException.class);
- }
-
- @BeforeEach
- void setup() {
- dynamoDBPersistenceStore = DynamoDBPersistenceStore.builder()
- .withTableName(TABLE_NAME)
- .withDynamoDbClient(client)
- .build();
- }
-
- @AfterEach
- void emptyDB() {
- // Clear all items from the table
- client.scan(ScanRequest.builder().tableName(TABLE_NAME).build())
- .items()
- .forEach(item -> {
- Map itemKey = Collections.singletonMap("id", item.get("id"));
- client.deleteItem(DeleteItemRequest.builder().tableName(TABLE_NAME).key(itemKey).build());
- });
- key = null;
- }
}
diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml
index 1cf3bf265..6e0113ea6 100644
--- a/powertools-logging/powertools-logging-log4j/pom.xml
+++ b/powertools-logging/powertools-logging-log4j/pom.xml
@@ -120,6 +120,9 @@
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
+
+ on-demand
+
diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java
index 46b5b65d4..4716f666a 100644
--- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java
+++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java
@@ -79,7 +79,7 @@ void shouldLogInJsonFormat() {
File logFile = new File("target/logfile.json");
assertThat(contentOf(logFile)).contains(
"{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":\"1\",\"service\":\"testLog4j\",\"timestamp\":")
- .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n");
+ .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\r\n");
}
@Test
@@ -89,7 +89,7 @@ void shouldLogInEcsFormat() {
File logFile = new File("target/ecslogfile.json");
assertThat(contentOf(logFile)).contains(
- "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"123456789012\",\"faas.id\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"faas.name\":\"test-function\",\"faas.version\":\"1\",\"faas.memory\":128,\"faas.execution\":\"test-request-id\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n");
+ "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"123456789012\",\"faas.id\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"faas.name\":\"test-function\",\"faas.version\":\"1\",\"faas.memory\":128,\"faas.execution\":\"test-request-id\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\r\n");
}
}
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..b489dcb5a 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
@@ -163,7 +163,7 @@ void shouldLogException() {
// THEN (stack is logged with root cause first)
assertThat(result).contains(
- "\"message\":\"Error\",\"error.message\":\"Unexpected value\",\"error.type\":\"java.lang.IllegalStateException\",\"error.stack_trace\":\"java.lang.IllegalStateException: Unexpected value\\n");
+ "\"message\":\"Error\",\"error.message\":\"Unexpected value\",\"error.type\":\"java.lang.IllegalStateException\",\"error.stack_trace\":\"java.lang.IllegalStateException: Unexpected value\\r\\n");
}
private void setMDC() {
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..0008c98ee 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
@@ -68,378 +68,384 @@
@Order(2)
class LambdaJsonEncoderTest {
- private static final Logger logger = (Logger) LoggerFactory.getLogger(LambdaJsonEncoderTest.class.getName());
- private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null);
-
- private Context context;
-
- @BeforeEach
- void setUp() throws IllegalAccessException, IOException {
- MDC.clear();
- // Reset cold start state
- writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true);
- writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true);
-
- context = new TestLambdaContext();
- // Make sure file is cleaned up before running tests
- try {
- FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close();
- } catch (NoSuchFileException e) {
- // file may not exist on the first launch
+ private static final Logger logger = (Logger) LoggerFactory.getLogger(LambdaJsonEncoderTest.class.getName());
+ private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null);
+
+ private Context context;
+
+ @BeforeEach
+ void setUp() throws IllegalAccessException, IOException {
+ MDC.clear();
+ // Reset cold start state
+ writeStaticField(LambdaHandlerProcessor.class, "isColdStart", null, true);
+ writeStaticField(PowertoolsLogging.class, "hasBeenInitialized", new AtomicBoolean(false), true);
+
+ context = new TestLambdaContext();
+ // Make sure file is cleaned up before running tests
+ try {
+ FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0)
+ .close();
+ } catch (NoSuchFileException e) {
+ // file may not exist on the first launch
+ }
}
- }
-
- @AfterEach
- void cleanUp() throws IOException {
- try {
- FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close();
- } catch (NoSuchFileException e) {
- // file may not exist on the first launch
+
+ @AfterEach
+ void cleanUp() throws IOException {
+ try {
+ FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0)
+ .close();
+ } catch (NoSuchFileException e) {
+ // file may not exist on the first launch
+ }
+ }
+
+ @Test
+ void shouldLogInJsonFormat() {
+ // GIVEN
+ PowertoolsLogEnabled handler = new PowertoolsLogEnabled();
+
+ // WHEN
+ handler.handleRequest("Input", context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile)).contains(
+ "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":1,\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\",\"timestamp\":");
+ }
+
+ @Test
+ void shouldLogArgumentsAsJsonWhenUsingRawJson() {
+ // GIVEN
+ PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.JSON);
+ SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
+ msg.setMessageId("1212abcd");
+ msg.setBody("plop");
+ msg.setEventSource("eb");
+ msg.setAwsRegion("eu-central-1");
+ SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
+ attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
+ msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
+
+ // WHEN
+ requestHandler.handleRequest(msg, context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains(
+ "\"input\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}")
+ .contains("\"message\":\"1212abcd\"")
+ // Should auto-escape double quotes around id
+ .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"")
+ .contains("\"correlation_id\":\"1212abcd\"");
+ // Reserved keys should be ignored
+ PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> {
+ assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\"");
+ assertThat(contentOf(logFile)).contains(
+ "\"message\":\"Attempted to use reserved key '" + reservedKey
+ + "' in structured argument. This key will be ignored.\"");
+ });
+ }
+
+ @Test
+ void shouldLogArgumentsAsJsonWhenUsingKeyValue() {
+ // GIVEN
+ PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.ENTRY);
+ SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
+ msg.setMessageId("1212abcd");
+ msg.setBody("plop");
+ msg.setEventSource("eb");
+ msg.setAwsRegion("eu-central-1");
+ SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
+ attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
+ msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
+
+ // WHEN
+ requestHandler.handleRequest(msg, context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains(
+ "\"input\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}")
+ .contains("\"message\":\"1212abcd\"")
+ // Should auto-escape double quotes around id
+ .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"")
+ .contains("\"correlation_id\":\"1212abcd\"");
+ // Reserved keys should be ignored
+ PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> {
+ assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\"");
+ assertThat(contentOf(logFile)).contains(
+ "\"message\":\"Attempted to use reserved key '" + reservedKey
+ + "' in structured argument. This key will be ignored.\"");
+ });
+ }
+
+ @Test
+ void shouldNotLogPowertoolsInfo() {
+ // GIVEN
+ LambdaJsonEncoder encoder = new LambdaJsonEncoder();
+
+ MDC.put(PowertoolsLoggedFields.FUNCTION_NAME.getName(), context.getFunctionName());
+ MDC.put(PowertoolsLoggedFields.FUNCTION_ARN.getName(), context.getInvokedFunctionArn());
+ MDC.put(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), context.getFunctionVersion());
+ MDC.put(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName(),
+ String.valueOf(context.getMemoryLimitInMB()));
+ MDC.put(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName(), context.getAwsRequestId());
+ MDC.put(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "false");
+ MDC.put(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.2");
+ MDC.put(PowertoolsLoggedFields.SERVICE.getName(), "Service");
+
+ // WHEN
+ byte[] encoded = encoder.encode(loggingEvent);
+ String result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN
+ assertThat(result).contains(
+ "{\"level\":\"INFO\",\"message\":\"message\",\"cold_start\":false,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":1,\"sampling_rate\":0.2,\"service\":\"Service\",\"timestamp\":");
+
+ // WHEN (powertoolsInfo = false)
+ encoder.setIncludePowertoolsInfo(false);
+ encoded = encoder.encode(loggingEvent);
+ result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN (no powertools info in logs)
+ assertThat(result).doesNotContain("cold_start", "function_arn", "function_memory_size", "function_name",
+ "function_request_id", "function_version", "sampling_rate", "service");
+ }
+
+ @Test
+ void shouldLogStructuredArgumentsAsNewEntries() {
+ // GIVEN
+ LambdaJsonEncoder encoder = new LambdaJsonEncoder();
+
+ SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
+ msg.setMessageId("1212abcd");
+ msg.setBody("plop");
+ msg.setEventSource("eb");
+ msg.setAwsRegion("eu-central-1");
+ SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
+ attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
+ msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
+ StructuredArgument argument = StructuredArguments.entry("msg", msg);
+
+ // WHEN
+ LoggingEvent structuredLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "A message", null,
+ new Object[] { argument });
+ byte[] encoded = encoder.encode(structuredLoggingEvent);
+ String result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN (logged as JSON)
+ assertThat(result)
+ .contains(
+ "\"message\":\"A message\",\"msg\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}");
+ }
+
+ @Test
+ void shouldLogEventForHandlerWithLogEventAnnotation() {
+ // GIVEN
+ PowertoolsLogEvent requestHandler = new PowertoolsLogEvent();
+
+ // WHEN
+ requestHandler.handleRequest(singletonList("ListOfOneElement"), context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile)).contains("\"event\":[\"ListOfOneElement\"]");
}
- }
-
- @Test
- void shouldLogInJsonFormat() {
- // GIVEN
- PowertoolsLogEnabled handler = new PowertoolsLogEnabled();
-
- // WHEN
- handler.handleRequest("Input", context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile)).contains(
- "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":1,\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\",\"timestamp\":");
- }
-
- @Test
- void shouldLogArgumentsAsJsonWhenUsingRawJson() {
- // GIVEN
- PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.JSON);
- SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
- msg.setMessageId("1212abcd");
- msg.setBody("plop");
- msg.setEventSource("eb");
- msg.setAwsRegion("eu-central-1");
- SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
- attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
- msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
-
- // WHEN
- requestHandler.handleRequest(msg, context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains(
- "\"input\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}")
- .contains("\"message\":\"1212abcd\"")
- // Should auto-escape double quotes around id
- .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"")
- .contains("\"correlation_id\":\"1212abcd\"");
- // Reserved keys should be ignored
- PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> {
- assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\"");
- assertThat(contentOf(logFile)).contains(
- "\"message\":\"Attempted to use reserved key '" + reservedKey
- + "' in structured argument. This key will be ignored.\"");
- });
- }
-
- @Test
- void shouldLogArgumentsAsJsonWhenUsingKeyValue() {
- // GIVEN
- PowertoolsArguments requestHandler = new PowertoolsArguments(PowertoolsArguments.ArgumentFormat.ENTRY);
- SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
- msg.setMessageId("1212abcd");
- msg.setBody("plop");
- msg.setEventSource("eb");
- msg.setAwsRegion("eu-central-1");
- SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
- attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
- msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
-
- // WHEN
- requestHandler.handleRequest(msg, context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains(
- "\"input\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}")
- .contains("\"message\":\"1212abcd\"")
- // Should auto-escape double quotes around id
- .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"")
- .contains("\"correlation_id\":\"1212abcd\"");
- // Reserved keys should be ignored
- PowertoolsLoggedFields.stringValues().stream().forEach(reservedKey -> {
- assertThat(contentOf(logFile)).doesNotContain("\"" + reservedKey + "\":\"shouldBeIgnored\"");
- assertThat(contentOf(logFile)).contains(
- "\"message\":\"Attempted to use reserved key '" + reservedKey
- + "' in structured argument. This key will be ignored.\"");
- });
- }
-
- @Test
- void shouldNotLogPowertoolsInfo() {
- // GIVEN
- LambdaJsonEncoder encoder = new LambdaJsonEncoder();
-
- MDC.put(PowertoolsLoggedFields.FUNCTION_NAME.getName(), context.getFunctionName());
- MDC.put(PowertoolsLoggedFields.FUNCTION_ARN.getName(), context.getInvokedFunctionArn());
- MDC.put(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), context.getFunctionVersion());
- MDC.put(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName(),
- String.valueOf(context.getMemoryLimitInMB()));
- MDC.put(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName(), context.getAwsRequestId());
- MDC.put(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "false");
- MDC.put(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.2");
- MDC.put(PowertoolsLoggedFields.SERVICE.getName(), "Service");
-
- // WHEN
- byte[] encoded = encoder.encode(loggingEvent);
- String result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN
- assertThat(result).contains(
- "{\"level\":\"INFO\",\"message\":\"message\",\"cold_start\":false,\"function_arn\":\"arn:aws:lambda:us-east-1:123456789012:function:test\",\"function_memory_size\":128,\"function_name\":\"test-function\",\"function_request_id\":\"test-request-id\",\"function_version\":1,\"sampling_rate\":0.2,\"service\":\"Service\",\"timestamp\":");
-
- // WHEN (powertoolsInfo = false)
- encoder.setIncludePowertoolsInfo(false);
- encoded = encoder.encode(loggingEvent);
- result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN (no powertools info in logs)
- assertThat(result).doesNotContain("cold_start", "function_arn", "function_memory_size", "function_name",
- "function_request_id", "function_version", "sampling_rate", "service");
- }
-
- @Test
- void shouldLogStructuredArgumentsAsNewEntries() {
- // GIVEN
- LambdaJsonEncoder encoder = new LambdaJsonEncoder();
-
- SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
- msg.setMessageId("1212abcd");
- msg.setBody("plop");
- msg.setEventSource("eb");
- msg.setAwsRegion("eu-central-1");
- SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
- attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
- msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
- StructuredArgument argument = StructuredArguments.entry("msg", msg);
-
- // WHEN
- LoggingEvent structuredLoggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "A message", null,
- new Object[] { argument });
- byte[] encoded = encoder.encode(structuredLoggingEvent);
- String result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN (logged as JSON)
- assertThat(result)
- .contains(
- "\"message\":\"A message\",\"msg\":{\"awsRegion\":\"eu-central-1\",\"body\":\"plop\",\"eventSource\":\"eb\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}},\"messageId\":\"1212abcd\"}");
- }
-
- @Test
- void shouldLogEventForHandlerWithLogEventAnnotation() {
- // GIVEN
- PowertoolsLogEvent requestHandler = new PowertoolsLogEvent();
-
- // WHEN
- requestHandler.handleRequest(singletonList("ListOfOneElement"), context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile)).contains("\"event\":[\"ListOfOneElement\"]");
- }
-
- @Test
- void shouldLogEventForHandlerWhenEnvVariableSetToTrue() {
- try {
- // GIVEN
- LoggingConstants.POWERTOOLS_LOG_EVENT = true;
-
- PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled();
-
- SQSEvent.SQSMessage message = new SQSEvent.SQSMessage();
- message.setBody("body");
- message.setMessageId("1234abcd");
- message.setAwsRegion("eu-central-1");
-
- // WHEN
- requestHandler.handleRequest(message, context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains("\"message\":\"Handler Event\"")
- .contains(
- "\"event\":{\"awsRegion\":\"eu-central-1\",\"body\":\"body\",\"messageId\":\"1234abcd\"}");
- } finally {
- LoggingConstants.POWERTOOLS_LOG_EVENT = false;
+
+ @Test
+ void shouldLogEventForHandlerWhenEnvVariableSetToTrue() {
+ try {
+ // GIVEN
+ LoggingConstants.POWERTOOLS_LOG_EVENT = true;
+
+ PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled();
+
+ SQSEvent.SQSMessage message = new SQSEvent.SQSMessage();
+ message.setBody("body");
+ message.setMessageId("1234abcd");
+ message.setAwsRegion("eu-central-1");
+
+ // WHEN
+ requestHandler.handleRequest(message, context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains("\"message\":\"Handler Event\"")
+ .contains(
+ "\"event\":{\"awsRegion\":\"eu-central-1\",\"body\":\"body\",\"messageId\":\"1234abcd\"}");
+ } finally {
+ LoggingConstants.POWERTOOLS_LOG_EVENT = false;
+ }
}
- }
-
- @Test
- void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException {
- // GIVEN
- LoggingConstants.POWERTOOLS_LOG_EVENT = false;
-
- // WHEN
- PowertoolsLogEventDisabled requestHandler = new PowertoolsLogEventDisabled();
- requestHandler.handleRequest(singletonList("ListOfOneElement"), context);
-
- // THEN
- Assertions.assertEquals(0,
- Files.lines(Paths.get("target/logfile.json")).collect(joining()).length());
- }
-
- @Test
- void shouldLogEventAsStringForStreamHandler() throws IOException {
- // GIVEN
- PowertoolsLogEventForStream requestStreamHandler = new PowertoolsLogEventForStream();
- ByteArrayOutputStream output = new ByteArrayOutputStream();
-
- // WHEN
- requestStreamHandler.handleRequest(
- new ByteArrayInputStream(
- new ObjectMapper().writeValueAsBytes(Collections.singletonMap("key", "value"))),
- output, context);
-
- // THEN
- assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8))
- .isNotEmpty();
-
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains("\"message\":\"Handler Event\"")
- // logged as String for StreamHandler (should auto-escape double-quotes to avoid breaking JSON format)
- .contains("\"event\":\"{\\\"key\\\":\\\"value\\\"}\"");
- }
-
- @Test
- void shouldLogResponseForHandlerWithLogResponseAnnotation() {
- // GIVEN
- PowertoolsLogResponse requestHandler = new PowertoolsLogResponse();
-
- // WHEN
- requestHandler.handleRequest("input", context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains("\"message\":\"Handler Response\"")
- .contains("\"response\":\"Hola mundo\"");
- }
-
- @Test
- void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() {
- try {
- // GIVEN
- LoggingConstants.POWERTOOLS_LOG_RESPONSE = true;
-
- PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled();
-
- // WHEN
- requestHandler.handleRequest("input", context);
-
- // THEN
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains("\"message\":\"Handler Response\"")
- .contains("\"response\":\"Bonjour le monde\"");
- } finally {
- LoggingConstants.POWERTOOLS_LOG_RESPONSE = false;
+
+ @Test
+ void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException {
+ // GIVEN
+ LoggingConstants.POWERTOOLS_LOG_EVENT = false;
+
+ // WHEN
+ PowertoolsLogEventDisabled requestHandler = new PowertoolsLogEventDisabled();
+ requestHandler.handleRequest(singletonList("ListOfOneElement"), context);
+
+ // THEN
+ Assertions.assertEquals(0,
+ Files.lines(Paths.get("target/logfile.json")).collect(joining()).length());
+ }
+
+ @Test
+ void shouldLogEventAsStringForStreamHandler() throws IOException {
+ // GIVEN
+ PowertoolsLogEventForStream requestStreamHandler = new PowertoolsLogEventForStream();
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ // WHEN
+ requestStreamHandler.handleRequest(
+ new ByteArrayInputStream(
+ new ObjectMapper().writeValueAsBytes(
+ Collections.singletonMap("key", "value"))),
+ output, context);
+
+ // THEN
+ assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8))
+ .isNotEmpty();
+
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains("\"message\":\"Handler Event\"")
+ // logged as String for StreamHandler (should auto-escape double-quotes to avoid
+ // breaking JSON format)
+ .contains("\"event\":\"{\\\"key\\\":\\\"value\\\"}\"");
+ }
+
+ @Test
+ void shouldLogResponseForHandlerWithLogResponseAnnotation() {
+ // GIVEN
+ PowertoolsLogResponse requestHandler = new PowertoolsLogResponse();
+
+ // WHEN
+ requestHandler.handleRequest("input", context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains("\"message\":\"Handler Response\"")
+ .contains("\"response\":\"Hola mundo\"");
+ }
+
+ @Test
+ void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() {
+ try {
+ // GIVEN
+ LoggingConstants.POWERTOOLS_LOG_RESPONSE = true;
+
+ PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled();
+
+ // WHEN
+ requestHandler.handleRequest("input", context);
+
+ // THEN
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains("\"message\":\"Handler Response\"")
+ .contains("\"response\":\"Bonjour le monde\"");
+ } finally {
+ LoggingConstants.POWERTOOLS_LOG_RESPONSE = false;
+ }
+ }
+
+ @Test
+ void shouldLogResponseForStreamHandler() throws IOException {
+ // GIVEN
+ PowertoolsLogResponseForStream requestStreamHandler = new PowertoolsLogResponseForStream();
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ String input = "BobThe Sponge";
+
+ // WHEN
+ requestStreamHandler.handleRequest(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)),
+ output,
+ context);
+
+ // THEN
+ assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8))
+ .isEqualTo(input);
+
+ File logFile = new File("target/logfile.json");
+ assertThat(contentOf(logFile))
+ .contains("\"message\":\"Handler Response\"")
+ .contains("\"response\":\"" + input + "\"");
+ }
+
+ @Test
+ void shouldLogThreadInfo() {
+ // GIVEN
+ LambdaJsonEncoder encoder = new LambdaJsonEncoder();
+ encoder.setIncludeThreadInfo(true);
+
+ // WHEN
+ byte[] encoded = encoder.encode(loggingEvent);
+ String result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN
+ assertThat(result).contains(
+ "\"thread\":\"main\",\"thread_id\":" + Thread.currentThread().getId()
+ + ",\"thread_priority\":5");
+ }
+
+ @Test
+ void shouldLogTimestampDifferently() {
+ // GIVEN
+ LambdaJsonEncoder encoder = new LambdaJsonEncoder();
+ String pattern = "yyyy-MM-dd_HH";
+ String timeZone = "Europe/Paris";
+ encoder.setTimestampFormat(pattern);
+ encoder.setTimestampFormatTimezoneId(timeZone);
+
+ // WHEN
+ Date date = new Date();
+ byte[] encoded = encoder.encode(loggingEvent);
+ String result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
+ simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timeZone));
+ assertThat(result).contains("\"timestamp\":\"" + simpleDateFormat.format(date) + "\"");
+ }
+
+ @Test
+ void shouldLogException() {
+ // GIVEN
+ LambdaJsonEncoder encoder = new LambdaJsonEncoder();
+ encoder.start();
+ LoggingEvent errorloggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Error",
+ new IllegalStateException("Unexpected value"), null);
+
+ // WHEN
+ byte[] encoded = encoder.encode(errorloggingEvent);
+ String result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN
+ assertThat(result).contains("\"message\":\"Error\",\"error\":{")
+ .contains("\"message\":\"Unexpected value\"")
+ .contains("\"name\":\"java.lang.IllegalStateException\"")
+ .contains(
+ "\"stack\":\"[software.amazon.lambda.powertools.logging.internal.LambdaJsonEncoderTest.shouldLogException");
+
+ // WHEN (configure a custom throwableConverter)
+ encoder = new LambdaJsonEncoder();
+ RootCauseFirstThrowableProxyConverter throwableConverter = new RootCauseFirstThrowableProxyConverter();
+ encoder.setThrowableConverter(throwableConverter);
+ encoder.start();
+ encoded = encoder.encode(errorloggingEvent);
+ result = new String(encoded, StandardCharsets.UTF_8);
+
+ // THEN (stack is logged with root cause first)
+ assertThat(result).contains("\"message\":\"Unexpected value\"")
+ .contains("\"name\":\"java.lang.IllegalStateException\"")
+ .contains("\"stack\":\"java.lang.IllegalStateException: Unexpected value\\r\\n");
}
- }
-
- @Test
- void shouldLogResponseForStreamHandler() throws IOException {
- // GIVEN
- PowertoolsLogResponseForStream requestStreamHandler = new PowertoolsLogResponseForStream();
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- String input = "BobThe Sponge";
-
- // WHEN
- requestStreamHandler.handleRequest(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), output,
- context);
-
- // THEN
- assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8))
- .isEqualTo(input);
-
- File logFile = new File("target/logfile.json");
- assertThat(contentOf(logFile))
- .contains("\"message\":\"Handler Response\"")
- .contains("\"response\":\"" + input + "\"");
- }
-
- @Test
- void shouldLogThreadInfo() {
- // GIVEN
- LambdaJsonEncoder encoder = new LambdaJsonEncoder();
- encoder.setIncludeThreadInfo(true);
-
- // WHEN
- byte[] encoded = encoder.encode(loggingEvent);
- String result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN
- assertThat(result).contains(
- "\"thread\":\"main\",\"thread_id\":" + Thread.currentThread().getId() + ",\"thread_priority\":5");
- }
-
- @Test
- void shouldLogTimestampDifferently() {
- // GIVEN
- LambdaJsonEncoder encoder = new LambdaJsonEncoder();
- String pattern = "yyyy-MM-dd_HH";
- String timeZone = "Europe/Paris";
- encoder.setTimestampFormat(pattern);
- encoder.setTimestampFormatTimezoneId(timeZone);
-
- // WHEN
- Date date = new Date();
- byte[] encoded = encoder.encode(loggingEvent);
- String result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
- simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timeZone));
- assertThat(result).contains("\"timestamp\":\"" + simpleDateFormat.format(date) + "\"");
- }
-
- @Test
- void shouldLogException() {
- // GIVEN
- LambdaJsonEncoder encoder = new LambdaJsonEncoder();
- encoder.start();
- LoggingEvent errorloggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Error",
- new IllegalStateException("Unexpected value"), null);
-
- // WHEN
- byte[] encoded = encoder.encode(errorloggingEvent);
- String result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN
- assertThat(result).contains("\"message\":\"Error\",\"error\":{")
- .contains("\"message\":\"Unexpected value\"")
- .contains("\"name\":\"java.lang.IllegalStateException\"")
- .contains(
- "\"stack\":\"[software.amazon.lambda.powertools.logging.internal.LambdaJsonEncoderTest.shouldLogException");
-
- // WHEN (configure a custom throwableConverter)
- encoder = new LambdaJsonEncoder();
- RootCauseFirstThrowableProxyConverter throwableConverter = new RootCauseFirstThrowableProxyConverter();
- encoder.setThrowableConverter(throwableConverter);
- encoder.start();
- encoded = encoder.encode(errorloggingEvent);
- result = new String(encoded, StandardCharsets.UTF_8);
-
- // THEN (stack is logged with root cause first)
- assertThat(result).contains("\"message\":\"Unexpected value\"")
- .contains("\"name\":\"java.lang.IllegalStateException\"")
- .contains("\"stack\":\"java.lang.IllegalStateException: Unexpected value\\n");
- }
}
diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java
index fac85e230..e09a953bc 100644
--- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java
+++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/KeyBufferTest.java
@@ -131,7 +131,8 @@ void shouldEvictMultipleSmallEventsForLargeValidEvent() {
Deque events = buffer.removeAll("key1");
// Should only contain the last few small events plus the large event
- // 18 bytes for large event leaves 2 bytes, so only "ij" should remain with the large event
+ // 18 bytes for large event leaves 2 bytes, so only "ij" should remain with the
+ // large event
assertThat(events).containsExactly("ij", "123456789012345678");
}
@@ -353,7 +354,8 @@ void shouldUseDefaultWarningLoggerWhenNotProvided() {
// Assert System.err received the warning
assertThat(errCapture)
.hasToString(
- "WARN [KeyBuffer] - Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer.\n");
+ "WARN [KeyBuffer] - Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer."
+ + System.lineSeparator());
} finally {
System.setErr(originalErr);
}