diff --git a/docs/core/tracing.md b/docs/core/tracing.md index 48e33d63a..5c08d5c5a 100644 --- a/docs/core/tracing.md +++ b/docs/core/tracing.md @@ -164,6 +164,38 @@ context for an operation using any native object. } ``` +## Override default object mapper + +You can optionally choose to override default object mapper which is used to serialize method response and exceptions when enabled. You might +want to supply custom object mapper in order to control how serialisation is done, for example, when you want to log only +specific fields from received event due to security. + +=== "App.java" + + ```java hl_lines="10-14" + import software.amazon.lambda.powertools.tracing.Tracing; + import software.amazon.lambda.powertools.tracing.TracingUtils; + import static software.amazon.lambda.powertools.tracing.CaptureMode.RESPONSE; + + /** + * Handler for requests to Lambda function. + */ + public class App implements RequestHandler { + static { + ObjectMapper objectMapper = new ObjectMapper(); + SimpleModule simpleModule = new SimpleModule(); + objectMapper.registerModule(simpleModule); + + TracingUtils.defaultObjectMapper(objectMapper); + } + + @Tracing(captureMode = RESPONSE) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + ... + } + } + ``` + ## Utilities Tracing modules comes with certain utility method when you don't want to use annotation for capturing a code block diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java index ec1416ad6..0e956e539 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java @@ -13,11 +13,11 @@ */ package software.amazon.lambda.powertools.tracing; +import java.util.function.Consumer; import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.entities.Entity; import com.amazonaws.xray.entities.Subsegment; - -import java.util.function.Consumer; +import com.fasterxml.jackson.databind.ObjectMapper; import static software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor.serviceName; @@ -27,6 +27,7 @@ * */ public final class TracingUtils { + private static ObjectMapper objectMapper; /** * Put an annotation to the current subsegment with a String value. @@ -155,4 +156,18 @@ public static void withSubsegment(String namespace, String name, Consumer { + static { + ObjectMapper objectMapper = new ObjectMapper(); + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(ChildClass.class, new ChildSerializer()); + objectMapper.registerModule(simpleModule); + + TracingUtils.defaultObjectMapper(objectMapper); + } + @Override + @Tracing(namespace = "lambdaHandler", captureMode = RESPONSE) + public Object handleRequest(Object input, Context context) { + ParentClass parentClass = new ParentClass("parent"); + ChildClass childClass = new ChildClass("child", parentClass); + parentClass.setC(childClass); + return parentClass; + } + + public class ParentClass { + public String name; + public ChildClass c; + + public ParentClass(String name) { + this.name = name; + } + + public void setC(ChildClass c) { + this.c = c; + } + } + + public class ChildClass { + public String name; + public ParentClass p; + + public ChildClass(String name, ParentClass p) { + this.name = name; + this.p = p; + } + } + + public static class ChildSerializer extends StdSerializer { + + public ChildSerializer() { + this(null); + } + + public ChildSerializer(Class t) { + super(t); + } + + @Override + public void serialize(ChildClass value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + jgen.writeStartObject(); + jgen.writeStringField("name", value.name); + jgen.writeStringField("p", value.p.name); + jgen.writeEndObject(); + } + } +} diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java index 415c501bf..8cd9b2f71 100644 --- a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/internal/LambdaTracingAspectTest.java @@ -16,7 +16,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; - import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; @@ -34,6 +33,7 @@ import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledExplicitlyForResponseAndError; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForError; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForResponse; +import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForResponseWithCustomMapper; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForStream; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledForStreamWithNoMetaData; import software.amazon.lambda.powertools.tracing.handlers.PowerTracerToolEnabledWithException; @@ -43,6 +43,7 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; @@ -85,7 +86,11 @@ void tearDown() { @Test void shouldCaptureNonHandlerMethod() { nonHandlerMethod.doSomething(); - assertThat(AWSXRay.getTraceEntity().getSubsegments()) + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .anySatisfy(segment -> assertThat(segment.getName()).isEqualTo("## doSomething")); @@ -94,7 +99,11 @@ void shouldCaptureNonHandlerMethod() { @Test void shouldCaptureNonHandlerMethodWithCustomSegmentName() { nonHandlerMethod.doSomethingCustomName(); - assertThat(AWSXRay.getTraceEntity().getSubsegments()) + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .anySatisfy(segment -> assertThat(segment.getName()).isEqualTo("custom")); @@ -104,6 +113,9 @@ void shouldCaptureNonHandlerMethodWithCustomSegmentName() { void shouldCaptureTraces() { requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -124,6 +136,9 @@ void shouldCaptureTracesWithExceptionMetaData() { Throwable exception = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -146,6 +161,9 @@ void shouldCaptureTracesWithExceptionMetaData() { void shouldCaptureTracesForStream() throws IOException { streamHandler.handleRequest(new ByteArrayInputStream("test".getBytes()), new ByteArrayOutputStream(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -165,6 +183,9 @@ void shouldNotCaptureTracesNotEnabled() throws IOException { requestHandler = new PowerToolDisabled(); requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .isEmpty(); @@ -181,6 +202,9 @@ void shouldCaptureTracesWithNoMetadata() { requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -200,6 +224,9 @@ void shouldCaptureTracesForStreamWithNoMetadata() throws IOException { streamHandler.handleRequest(new ByteArrayInputStream("test".getBytes()), new ByteArrayOutputStream(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -219,6 +246,9 @@ void shouldCaptureTracesWithNoMetadataDeprecated() { requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -240,6 +270,9 @@ void shouldNotCaptureTracesIfDisabledViaEnvironmentVariable() { requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -262,6 +295,9 @@ void shouldCaptureTracesIfExplicitlyEnabledAndEnvironmentVariableIsDisabled() { requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -277,6 +313,31 @@ void shouldCaptureTracesIfExplicitlyEnabledAndEnvironmentVariableIsDisabled() { } } + @Test + void shouldCaptureTracesForSelfReferencingReturnTypesViaCustomMapper() { + requestHandler = new PowerTracerToolEnabledForResponseWithCustomMapper(); + + requestHandler.handleRequest(new Object(), context); + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) + .hasSize(1) + .allSatisfy(subsegment -> { + assertThat(subsegment.getMetadata()) + .hasSize(1) + .containsKey("lambdaHandler"); + + assertThat(subsegment.getMetadata().get("lambdaHandler")) + .hasFieldOrPropertyWithValue("handleRequest response", "{\"name\":\"parent\",\"c\":{\"name\":\"child\",\"p\":\"parent\"}}"); + }); + + assertThatNoException().isThrownBy(AWSXRay::endSegment); + + AWSXRay.beginSegment(LambdaTracingAspectTest.class.getName()); + } + @Test void shouldCaptureTracesIfExplicitlyEnabledBothAndEnvironmentVariableIsDisabled() { try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { @@ -288,6 +349,9 @@ void shouldCaptureTracesIfExplicitlyEnabledBothAndEnvironmentVariableIsDisabled( requestHandler.handleRequest(new Object(), context); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { @@ -310,7 +374,13 @@ void shouldNotCaptureTracesWithExceptionMetaDataIfDisabledViaEnvironmentVariable mocked.when(() -> SystemWrapper.getenv("POWERTOOLS_TRACER_CAPTURE_ERROR")).thenReturn("false"); requestHandler = new PowerTracerToolEnabledWithException(); - catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); + Throwable throwable = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); + + assertThat(throwable) + .isInstanceOf(RuntimeException.class); + + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) @@ -335,6 +405,9 @@ void shouldCaptureTracesWithExceptionMetaDataEnabledExplicitlyAndEnvironmentVari Throwable exception = catchThrowable(() -> requestHandler.handleRequest(new Object(), context)); + assertThat(AWSXRay.getTraceEntity()) + .isNotNull(); + assertThat(AWSXRay.getTraceEntity().getSubsegmentsCopy()) .hasSize(1) .allSatisfy(subsegment -> { diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 6ff64c89b..b36b180cb 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -71,6 +71,10 @@ + + + + @@ -88,6 +92,10 @@ + + + +