Skip to content

Commit e2cfe37

Browse files
authored
Use aws-lambda-java-serialization library, which is available by default, while deserializing input and serializing output (#11868)
1 parent d480f15 commit e2cfe37

File tree

13 files changed

+1008
-256
lines changed

13 files changed

+1008
-256
lines changed

instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/TracingRequestStreamHandler.java

+26-19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.amazonaws.services.lambda.runtime.Context;
99
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
10+
import io.opentelemetry.api.trace.Span;
1011
import io.opentelemetry.context.Scope;
1112
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.ApiGatewayProxyRequest;
1213
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.AwsLambdaFunctionInstrumenter;
@@ -67,49 +68,55 @@ protected TracingRequestStreamHandler(
6768
@Override
6869
public void handleRequest(InputStream input, OutputStream output, Context context)
6970
throws IOException {
70-
7171
ApiGatewayProxyRequest proxyRequest = ApiGatewayProxyRequest.forStream(input);
72-
AwsLambdaRequest request =
73-
AwsLambdaRequest.create(context, proxyRequest, proxyRequest.getHeaders());
72+
AwsLambdaRequest request = createRequest(input, context, proxyRequest);
7473
io.opentelemetry.context.Context parentContext = instrumenter.extract(request);
7574

7675
if (!instrumenter.shouldStart(parentContext, request)) {
77-
doHandleRequest(proxyRequest.freshStream(), output, context);
76+
doHandleRequest(proxyRequest.freshStream(), output, context, request);
7877
return;
7978
}
8079

8180
io.opentelemetry.context.Context otelContext = instrumenter.start(parentContext, request);
81+
Throwable error = null;
8282
try (Scope ignored = otelContext.makeCurrent()) {
8383
doHandleRequest(
8484
proxyRequest.freshStream(),
85-
new OutputStreamWrapper(output, otelContext, request, openTelemetrySdk),
86-
context);
85+
new OutputStreamWrapper(output, otelContext),
86+
context,
87+
request);
8788
} catch (Throwable t) {
88-
instrumenter.end(otelContext, request, null, t);
89-
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
89+
error = t;
9090
throw t;
91+
} finally {
92+
instrumenter.end(otelContext, request, null, error);
93+
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
9194
}
9295
}
9396

97+
protected AwsLambdaRequest createRequest(
98+
InputStream input, Context context, ApiGatewayProxyRequest proxyRequest) throws IOException {
99+
return AwsLambdaRequest.create(context, proxyRequest, proxyRequest.getHeaders());
100+
}
101+
102+
protected void doHandleRequest(
103+
InputStream input, OutputStream output, Context context, AwsLambdaRequest request)
104+
throws IOException {
105+
doHandleRequest(input, output, context);
106+
}
107+
94108
protected abstract void doHandleRequest(InputStream input, OutputStream output, Context context)
95109
throws IOException;
96110

97-
private class OutputStreamWrapper extends OutputStream {
111+
private static class OutputStreamWrapper extends OutputStream {
98112

99113
private final OutputStream delegate;
100114
private final io.opentelemetry.context.Context otelContext;
101-
private final AwsLambdaRequest request;
102-
private final OpenTelemetrySdk openTelemetrySdk;
103115

104116
private OutputStreamWrapper(
105-
OutputStream delegate,
106-
io.opentelemetry.context.Context otelContext,
107-
AwsLambdaRequest request,
108-
OpenTelemetrySdk openTelemetrySdk) {
117+
OutputStream delegate, io.opentelemetry.context.Context otelContext) {
109118
this.delegate = delegate;
110119
this.otelContext = otelContext;
111-
this.request = request;
112-
this.openTelemetrySdk = openTelemetrySdk;
113120
}
114121

115122
@Override
@@ -135,8 +142,8 @@ public void flush() throws IOException {
135142
@Override
136143
public void close() throws IOException {
137144
delegate.close();
138-
instrumenter.end(otelContext, request, null, null);
139-
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
145+
Span span = Span.fromContext(otelContext);
146+
span.addEvent("Output stream closed");
140147
}
141148
}
142149
}

instrumentation/aws-lambda/aws-lambda-core-1.0/library/src/main/java/io/opentelemetry/instrumentation/awslambdacore/v1_0/TracingRequestStreamWrapper.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*/
2424
public class TracingRequestStreamWrapper extends TracingRequestStreamHandler {
2525

26-
private final WrappedLambda wrappedLambda;
26+
protected final WrappedLambda wrappedLambda;
2727

2828
public TracingRequestStreamWrapper() {
2929
this(
@@ -32,7 +32,8 @@ public TracingRequestStreamWrapper() {
3232
}
3333

3434
// Visible for testing
35-
TracingRequestStreamWrapper(OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) {
35+
protected TracingRequestStreamWrapper(
36+
OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) {
3637
super(openTelemetrySdk, WrapperConfiguration.flushTimeout());
3738
this.wrappedLambda = wrappedLambda;
3839
}

instrumentation/aws-lambda/aws-lambda-events-2.2/library/build.gradle.kts

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ dependencies {
1818
// in public API.
1919
library("com.amazonaws:aws-lambda-java-events:2.2.1")
2020

21+
// By default, "aws-lambda-java-serialization" library is enabled in the classpath
22+
// at the AWS Lambda environment except "java8" runtime which is deprecated.
23+
// But it is available at "java8.al2" runtime, so it is still can be used
24+
// by Java 8 based Lambda functions.
25+
// So that is the reason that why we add it as compile only dependency.
26+
compileOnly("com.amazonaws:aws-lambda-java-serialization:1.1.5")
27+
2128
// We need Jackson for wrappers to reproduce the serialization does when Lambda invokes a RequestHandler with event
2229
// since Lambda will only be able to invoke the wrapper itself with a generic Object.
2330
// Note that Lambda itself uses Jackson, but does not expose it to the function so we need to include it here.
@@ -33,6 +40,7 @@ dependencies {
3340
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
3441
testImplementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
3542
testImplementation("com.google.guava:guava")
43+
testImplementation("com.amazonaws:aws-lambda-java-serialization:1.1.5")
3644

3745
testImplementation(project(":instrumentation:aws-lambda:aws-lambda-events-2.2:testing"))
3846
testImplementation("uk.org.webcompere:system-stubs-jupiter")

instrumentation/aws-lambda/aws-lambda-events-2.2/library/src/main/java/io/opentelemetry/instrumentation/awslambdaevents/v2_2/LambdaParameters.java

+31
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.awslambdaevents.v2_2;
77

88
import com.amazonaws.services.lambda.runtime.Context;
9+
import java.io.InputStream;
910
import java.lang.reflect.Method;
1011
import java.util.function.BiFunction;
1112

@@ -27,5 +28,35 @@ static <T> Object[] toArray(
2728
return parameters;
2829
}
2930

31+
static <T> Object[] toParameters(Method targetMethod, T input, Context context) {
32+
Class<?>[] parameterTypes = targetMethod.getParameterTypes();
33+
Object[] parameters = new Object[parameterTypes.length];
34+
for (int i = 0; i < parameterTypes.length; i++) {
35+
Class<?> clazz = parameterTypes[i];
36+
boolean isContext = clazz.equals(Context.class);
37+
if (isContext) {
38+
parameters[i] = context;
39+
} else if (i == 0) {
40+
parameters[0] = input;
41+
}
42+
}
43+
return parameters;
44+
}
45+
46+
static Object toInput(
47+
Method targetMethod,
48+
InputStream inputStream,
49+
BiFunction<InputStream, Class<?>, Object> mapper) {
50+
Class<?>[] parameterTypes = targetMethod.getParameterTypes();
51+
for (int i = 0; i < parameterTypes.length; i++) {
52+
Class<?> clazz = parameterTypes[i];
53+
boolean isContext = clazz.equals(Context.class);
54+
if (i == 0 && !isContext) {
55+
return mapper.apply(inputStream, clazz);
56+
}
57+
}
58+
return null;
59+
}
60+
3061
private LambdaParameters() {}
3162
}

instrumentation/aws-lambda/aws-lambda-events-2.2/library/src/main/java/io/opentelemetry/instrumentation/awslambdaevents/v2_2/TracingRequestApiGatewayWrapper.java

+4-13
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import com.amazonaws.services.lambda.runtime.Context;
99
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
1010
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
11-
import com.fasterxml.jackson.core.JsonProcessingException;
1211
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.WrappedLambda;
12+
import io.opentelemetry.instrumentation.awslambdaevents.v2_2.internal.SerializationUtil;
1313
import io.opentelemetry.sdk.OpenTelemetrySdk;
1414
import java.util.function.BiFunction;
1515

@@ -35,12 +35,7 @@ public TracingRequestApiGatewayWrapper() {
3535

3636
// Visible for testing
3737
static <T> T map(APIGatewayProxyRequestEvent event, Class<T> clazz) {
38-
try {
39-
return OBJECT_MAPPER.readValue(event.getBody(), clazz);
40-
} catch (JsonProcessingException e) {
41-
throw new IllegalStateException(
42-
"Could not map API Gateway event body to requested parameter type: " + clazz, e);
43-
}
38+
return SerializationUtil.fromJson(event.getBody(), clazz);
4439
}
4540

4641
@Override
@@ -52,12 +47,8 @@ protected APIGatewayProxyResponseEvent doHandleRequest(
5247
if (result instanceof APIGatewayProxyResponseEvent) {
5348
event = (APIGatewayProxyResponseEvent) result;
5449
} else {
55-
try {
56-
event = new APIGatewayProxyResponseEvent();
57-
event.setBody(OBJECT_MAPPER.writeValueAsString(result));
58-
} catch (JsonProcessingException e) {
59-
throw new IllegalStateException("Could not serialize return value.", e);
60-
}
50+
event = new APIGatewayProxyResponseEvent();
51+
event.setBody(SerializationUtil.toJson(result));
6152
}
6253
return event;
6354
}

instrumentation/aws-lambda/aws-lambda-events-2.2/library/src/main/java/io/opentelemetry/instrumentation/awslambdaevents/v2_2/TracingRequestWrapper.java

+71-13
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,90 @@
55

66
package io.opentelemetry.instrumentation.awslambdaevents.v2_2;
77

8+
import com.amazonaws.services.lambda.runtime.Context;
9+
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
10+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest;
11+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.TracingRequestStreamWrapper;
12+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.ApiGatewayProxyRequest;
13+
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.MapUtils;
814
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.WrappedLambda;
15+
import io.opentelemetry.instrumentation.awslambdaevents.v2_2.internal.SerializationUtil;
916
import io.opentelemetry.sdk.OpenTelemetrySdk;
10-
import java.util.function.BiFunction;
17+
import java.io.ByteArrayInputStream;
18+
import java.io.ByteArrayOutputStream;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.OutputStream;
22+
import java.lang.reflect.InvocationTargetException;
23+
import java.lang.reflect.Method;
24+
import java.util.Collections;
25+
import java.util.Map;
1126

1227
/**
13-
* Wrapper for {@link io.opentelemetry.instrumentation.awslambdacore.v1_0.TracingRequestHandler}.
14-
* Allows for wrapping a regular lambda, not proxied through API Gateway. Therefore, HTTP headers
15-
* propagation is not supported.
28+
* Wrapper for {@link com.amazonaws.services.lambda.runtime.RequestHandler} based Lambda handlers.
1629
*/
17-
public class TracingRequestWrapper extends TracingRequestWrapperBase<Object, Object> {
30+
public class TracingRequestWrapper extends TracingRequestStreamWrapper {
1831
public TracingRequestWrapper() {
19-
super(TracingRequestWrapper::map);
32+
super();
2033
}
2134

2235
// Visible for testing
23-
TracingRequestWrapper(
24-
OpenTelemetrySdk openTelemetrySdk,
25-
WrappedLambda wrappedLambda,
26-
BiFunction<Object, Class<?>, Object> mapper) {
27-
super(openTelemetrySdk, wrappedLambda, mapper);
36+
TracingRequestWrapper(OpenTelemetrySdk openTelemetrySdk, WrappedLambda wrappedLambda) {
37+
super(openTelemetrySdk, wrappedLambda);
38+
}
39+
40+
@Override
41+
protected final AwsLambdaRequest createRequest(
42+
InputStream inputStream, Context context, ApiGatewayProxyRequest proxyRequest) {
43+
Method targetMethod = wrappedLambda.getRequestTargetMethod();
44+
Object input = LambdaParameters.toInput(targetMethod, inputStream, TracingRequestWrapper::map);
45+
return AwsLambdaRequest.create(context, input, extractHeaders(input));
46+
}
47+
48+
protected Map<String, String> extractHeaders(Object input) {
49+
if (input instanceof APIGatewayProxyRequestEvent) {
50+
return MapUtils.emptyIfNull(((APIGatewayProxyRequestEvent) input).getHeaders());
51+
}
52+
return Collections.emptyMap();
53+
}
54+
55+
@Override
56+
protected final void doHandleRequest(
57+
InputStream input, OutputStream output, Context context, AwsLambdaRequest request) {
58+
Method targetMethod = wrappedLambda.getRequestTargetMethod();
59+
Object[] parameters = LambdaParameters.toParameters(targetMethod, request.getInput(), context);
60+
try {
61+
Object result = targetMethod.invoke(wrappedLambda.getTargetObject(), parameters);
62+
SerializationUtil.toJson(output, result);
63+
} catch (IllegalAccessException e) {
64+
throw new IllegalStateException("Method is inaccessible", e);
65+
} catch (InvocationTargetException e) {
66+
throw (e.getCause() instanceof RuntimeException
67+
? (RuntimeException) e.getCause()
68+
: new IllegalStateException(e.getTargetException()));
69+
}
70+
}
71+
72+
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
73+
// Used for testing
74+
<INPUT, OUTPUT> OUTPUT handleRequest(INPUT input, Context context) throws IOException {
75+
byte[] inputJsonData = SerializationUtil.toJsonData(input);
76+
ByteArrayInputStream inputStream = new ByteArrayInputStream(inputJsonData);
77+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
78+
79+
super.handleRequest(inputStream, outputStream, context);
80+
81+
byte[] outputJsonData = outputStream.toByteArray();
82+
return (OUTPUT)
83+
SerializationUtil.fromJson(
84+
new ByteArrayInputStream(outputJsonData),
85+
wrappedLambda.getRequestTargetMethod().getReturnType());
2886
}
2987

3088
// Visible for testing
31-
static <T> T map(Object jsonMap, Class<T> clazz) {
89+
static <T> T map(InputStream inputStream, Class<T> clazz) {
3290
try {
33-
return OBJECT_MAPPER.convertValue(jsonMap, clazz);
91+
return SerializationUtil.fromJson(inputStream, clazz);
3492
} catch (IllegalArgumentException e) {
3593
throw new IllegalStateException(
3694
"Could not map input to requested parameter type: " + clazz, e);

instrumentation/aws-lambda/aws-lambda-events-2.2/library/src/main/java/io/opentelemetry/instrumentation/awslambdaevents/v2_2/TracingRequestWrapperBase.java

-6
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
import com.amazonaws.services.lambda.runtime.Context;
99
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
10-
import com.fasterxml.jackson.databind.DeserializationFeature;
11-
import com.fasterxml.jackson.databind.ObjectMapper;
1210
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
1311
import io.opentelemetry.instrumentation.awslambdacore.v1_0.TracingRequestHandler;
1412
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.MapUtils;
@@ -29,10 +27,6 @@
2927
*/
3028
abstract class TracingRequestWrapperBase<I, O> extends TracingRequestHandler<I, O> {
3129

32-
protected static final ObjectMapper OBJECT_MAPPER =
33-
new ObjectMapper()
34-
.registerModule(new CustomJodaModule())
35-
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
3630
private final WrappedLambda wrappedLambda;
3731
private final Method targetMethod;
3832
private final BiFunction<I, Class<?>, Object> parameterMapper;
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
package io.opentelemetry.instrumentation.awslambdaevents.v2_2;
6+
package io.opentelemetry.instrumentation.awslambdaevents.v2_2.internal;
77

88
import com.fasterxml.jackson.core.JsonParser;
99
import com.fasterxml.jackson.databind.DeserializationContext;

0 commit comments

Comments
 (0)