diff --git a/docs/core/logging.md b/docs/core/logging.md index 2df9a4529..fd74db3d2 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -217,9 +217,7 @@ Key | Type | Example | Description ## Capturing context Lambda info -You can enrich your structured logs with key Lambda context information via `logEvent` annotation parameter. -You can also explicitly log any incoming event using `logEvent` param. Refer [Override default object mapper](#override-default-object-mapper) -to customise what is logged. +When debugging in non-production environments, you can instruct Logger to log the incoming event with `@Logger(logEvent = true)` or via `POWERTOOLS_LOGGER_LOG_EVENT=true` environment variable. !!! warning Log event is disabled by default to prevent sensitive info being logged. diff --git a/docs/index.md b/docs/index.md index 6af4c5e8d..92589be7c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -285,5 +285,7 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl | **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | | **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logging) | | **POWERTOOLS_LOG_LEVEL** | Sets logging level | [Logging](./core/logging) | +| **POWERTOOLS_LOGGER_LOG_EVENT** | Enables/Disables whether to log the incoming event when using the aspect | [Logging](./core/logging) | | **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Enables/Disables tracing mode to capture method response | [Tracing](./core/tracing) | | **POWERTOOLS_TRACER_CAPTURE_ERROR** | Enables/Disables tracing mode to capture method error | [Tracing](./core/tracing) | + diff --git a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java index 91c93c3f3..d4e18dddc 100644 --- a/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java +++ b/powertools-core/src/main/java/software/amazon/lambda/powertools/core/internal/LambdaHandlerProcessor.java @@ -47,6 +47,13 @@ public static boolean isHandlerMethod(final ProceedingJoinPoint pjp) { return placedOnRequestHandler(pjp) || placedOnStreamHandler(pjp); } + /** + * The class needs to implement RequestHandler interface + * The function needs to have exactly two arguments + * The second argument needs to be of type com.amazonaws.services.lambda.runtime.Context + * @param pjp + * @return + */ public static boolean placedOnRequestHandler(final ProceedingJoinPoint pjp) { return RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType()) && pjp.getArgs().length == 2 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 4a98735af..ff556d89a 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 @@ -33,6 +33,7 @@ import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -42,6 +43,7 @@ import java.util.Map; import java.util.Optional; import java.util.Random; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -65,6 +67,7 @@ public final class LambdaLoggingAspect { private static final String LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); private static final String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); + private static Boolean LOG_EVENT; private static Level LEVEL_AT_INITIALISATION; @@ -74,6 +77,13 @@ public final class LambdaLoggingAspect { } LEVEL_AT_INITIALISATION = LOG.getLevel(); + + String logEvent = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); + if (logEvent != null) { + LOG_EVENT = Boolean.parseBoolean(logEvent); + } else { + LOG_EVENT = false; + } } private static void resetLogLevels(Level logLevel) { @@ -104,7 +114,9 @@ public Object around(ProceedingJoinPoint pjp, getXrayTraceId().ifPresent(xRayTraceId -> appendKey("xray_trace_id", xRayTraceId)); - if (logging.logEvent()) { + // Check that the environment variable was enabled explicitly + // Or that the handler was annotated with @Logging(logEvent = true) + if (LOG_EVENT || logging.logEvent()) { proceedArgs = logEvent(pjp); } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventDisabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventDisabled.java new file mode 100644 index 000000000..77103e450 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventDisabled.java @@ -0,0 +1,28 @@ +/* + * 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.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowerToolLogEventDisabled implements RequestHandler { + + @Logging(logEvent = false) + @Override + public Object handleRequest(Object input, Context context) { + return null; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 6952fe755..75cf22aa5 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -14,19 +14,6 @@ package software.amazon.lambda.powertools.logging.internal; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; -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.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getProperty; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; - import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; @@ -37,24 +24,10 @@ import com.amazonaws.services.lambda.runtime.tests.annotations.Event; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -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.List; -import java.util.Map; -import java.util.stream.Collectors; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; import org.json.JSONException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -62,18 +35,33 @@ import org.mockito.MockedStatic; import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.core.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayRestApiCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled; -import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledWithCustomMapper; -import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabledWithClearState; -import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventBridgeCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.*; + +import java.io.*; +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.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.joining; +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getProperty; +import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; class LambdaLoggingAspectTest { @@ -116,7 +104,7 @@ void shouldSetLambdaContextWhenEnabled() { void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException { requestStreamHandler = new PowerLogToolEnabledForStream(); - requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); assertThat(ThreadContext.getImmutableContext()) @@ -132,14 +120,14 @@ void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException { @Test void shouldSetColdStartFlag() throws IOException { - requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); assertThat(ThreadContext.getImmutableContext()) .hasSize(EXPECTED_CONTEXT_SIZE) .containsEntry("coldStart", "true"); - requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), + requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); assertThat(ThreadContext.getImmutableContext()) @@ -195,6 +183,51 @@ void shouldLogEventForHandler() throws IOException, JSONException { assertEquals(expectEvent, event, false); } + /** + * If POWERTOOLS_LOGGER_LOG_EVENT was set to true, the handler should log, despite @Logging(logEvent=false) + * + * @throws IOException + */ + @Test + void shouldLogEventForHandlerWhenEnvVariableSetToTrue() throws IOException, IllegalAccessException, JSONException { + try { + writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", Boolean.TRUE, true); + + requestHandler = new PowerToolLogEventDisabled(); + S3EventNotification s3EventNotification = s3EventNotification(); + + requestHandler.handleRequest(s3EventNotification, context); + + Map log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); + + String event = (String) log.get("message"); + + String expectEvent = new BufferedReader( + new InputStreamReader(this.getClass().getResourceAsStream("/s3EventNotification.json"))) + .lines().collect(joining("\n")); + + assertEquals(expectEvent, event, false); + } finally { + writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", Boolean.FALSE, true); + } + } + + /** + * If POWERTOOLS_LOGGER_LOG_EVENT was set to false and @Logging(logEvent=false), the handler shouldn't log + * + * @throws IOException + */ + @Test + void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException { + requestHandler = new PowerToolLogEventDisabled(); + S3EventNotification s3EventNotification = s3EventNotification(); + + requestHandler.handleRequest(s3EventNotification, context); + + Assertions.assertEquals(0, + Files.lines(Paths.get("target/logfile.json")).collect(joining()).length()); + } + @Test void shouldLogEventForHandlerWithOverriddenObjectMapper() throws IOException, JSONException { RequestHandler handler = new PowerToolLogEventEnabledWithCustomMapper();