Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split lambda instrumentation into core and events #5326

Merged
merged 17 commits into from
Feb 11, 2022

Conversation

anuraaga
Copy link
Contributor

@anuraaga anuraaga commented Feb 9, 2022

Fixes #5325
Fixes #5285

Sorry for huge PR couldn't see a way to split up the PR since it's actually splitting up the code.

I had one big misunderstanding about the Java Lambda runtime, that it would include libraries like Jackson for our use. But it turns out unlike some other languages like Python, Java doesn't provide any libraries, presumably since Java provides good classloader isolation techniques.

This PR makes several changes

  • Splits instrumentation into two parts.

    • Target aws-lambda-java-core, which is mostly for supporting RequestStreamHandler. Notably, most lambda functions which use an actual web framework built on top of Lambda (using aws-serverless-java-container or AWS's internal framework) only include this dependency and do not include -events nor should it need to. Core is mostly about instrumenting and does not contain features like serialization.
    • Target aws-lambda-java-events, which is mostly for the lambda wrapper of RequestHandler, including support for events. Because Lambda does not know about the user's function type when invoking our wrapper, we must reproduce Lambda's serialization logic when invoking the actual user function.
      • Instrumenter logic only applying to events is only in this module
  • Include Jackson as a dependency

    • lambda-core depends on Jackson core for extracting headers for HTTP propagation. Ideally instrumentation doesn't need external dependencies but this feature is too important to lose in core I think
    • lambda-events depends on Jackson databind. In the future, we will replace that with depending on aws-lambda-java-serialization. I've been concerned about the compatibility of our object mapping and finally found that we can reuse Lambda's serialization logic through that logic. I suspect that all of these types are currently incompatible (except SQS which we have a special wrapper for).

Copy link
Contributor Author

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to explain some more interesting parts or large diffs in moved files

import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.AwsLambdaFunctionInstrumenter;
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.AwsLambdaFunctionInstrumenterFactory;

public final class AwsLambdaInstrumentationHelper {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchanged except for removing reference to SQS instrumenter

@Advice.Argument(1) Context context,
@Advice.Local("otelInput") AwsLambdaRequest input,
@Advice.Local("otelContext") io.opentelemetry.context.Context otelContext,
@Advice.Local("otelScope") Scope otelScope) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchanged except removing references to SQS instrumenter


final class LambdaParameters {

static <T> Object[] toArray(Method targetMethod, T input, Context context) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed mapper for core version of this util

* Returns the HTTP headers for the request extracted from the request handler's input. By
* default, returns empty headers.
*/
protected Map<String, String> extractHttpHeaders(I input) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most invasive change, since a handler that uses ApiGatewayProxyEvent would need to have its HTTP headers extracted. That being said, headers could be represented by input in other unknown ways so giving a hook into that could be considered generally useful (with limited practical use case).

if (noHttpPropagationNeeded()) {
return new NoopRequest(source);
}

if (source.markSupported()) {
return new MarkableApiGatewayProxyRequest(source);
}
// fallback
return new CopiedApiGatewayProxyRequest(source);
// It is known that the Lambda runtime passes ByteArrayInputStream's to functions, so gracefully
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is reasonable. Notably, if we were actually hitting the fallback codepath, it would often be failing with ClassNotFoundException since commons-io is not referenced at all by lambda libraries and would only have to be available by luck

import io.opentelemetry.instrumentation.awslambdaevents.v2_2.internal.AwsLambdaEventsInstrumenterFactory;
import io.opentelemetry.instrumentation.awslambdaevents.v2_2.internal.AwsLambdaSqsInstrumenterFactory;

public final class AwsLambdaInstrumentationHelper {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as helper in current HEAD code

}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this matcher, verified by testInstrumentation in other project

@@ -63,18 +61,24 @@ public static void onEnter(
@Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext,
@Advice.Local("otelMessageScope") Scope messageScope) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is same as current instrumentation in HEAD

It's probably not enough to figure out a way to share logic in the advice classes between the two javaagent instrumentations at this time

super(
openTelemetrySdk,
WrapperConfiguration.flushTimeout(),
AwsLambdaEventsInstrumenterFactory.createInstrumenter(openTelemetrySdk));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key change - the instrumenter factory in this module is aware of event objects


import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaRequest;
import io.opentelemetry.instrumentation.awslambdacore.v1_0.AwsLambdaRequest;
import io.opentelemetry.instrumentation.awslambdacore.v1_0.internal.AwsLambdaFunctionAttributesExtractor;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the core instrumentation logic, augmenting with event-specific logic

@mateuszrzeszutek mateuszrzeszutek requested a review from a user February 9, 2022 09:43
@anuraaga anuraaga added this to the 1.11.0 milestone Feb 10, 2022

implementation("io.opentelemetry:opentelemetry-extension-aws")

implementation("com.fasterxml.jackson.core:jackson-core")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think worth adding comment here (similar to your PR comment) since this is unusual

Comment on lines +21 to +24
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

compileOnly("commons-io:commons-io:2.2")
compileOnly("org.slf4j:slf4j-api")

implementation("com.fasterxml.jackson.core:jackson-databind")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and copy in comment here from your PR comment would be nice I think

implementation(project(":instrumentation:aws-lambda:aws-lambda-core-1.0:library"))

implementation(project(":instrumentation:aws-lambda:aws-lambda-events-2.2:library")) {
// Only needed by wrappers, not the javaagent. Muzzle will catch if we accidentally change this.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@trask trask merged commit f236b2d into open-telemetry:main Feb 11, 2022
RashmiRam pushed a commit to RashmiRam/opentelemetry-auto-instr-java that referenced this pull request May 23, 2022
* Split out lambda core instrumentation

* More

* Remove request wrapper from core

* Split events

* Dedupe

* More

* More renames

* Finish

* Clean

* README

* Rename more

* Finish

* Fix README

* Fix README

* Fix

* Comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants