Skip to content

Commit

Permalink
feat: Add support for POWERTOOLS_LOGGER_LOG_EVENT (#1510)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexeySoshin committed Nov 14, 2023
1 parent 0f3e896 commit de547d0
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 47 deletions.
4 changes: 1 addition & 3 deletions docs/core/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Object, Object> {

@Logging(logEvent = false)
@Override
public Object handleRequest(Object input, Context context) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,43 +24,44 @@
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;
import org.mockito.Mock;
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 {

Expand Down Expand Up @@ -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())
Expand All @@ -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())
Expand Down Expand Up @@ -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<String, Object> 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<S3EventNotification, Object> handler = new PowerToolLogEventEnabledWithCustomMapper();
Expand Down

0 comments on commit de547d0

Please sign in to comment.