-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
706 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
jar { | ||
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.aws-java-sdk-lambda-1.11.280' } | ||
} | ||
|
||
dependencies { | ||
implementation(project(":agent-bridge")) | ||
implementation(project(":agent-bridge-datastore")) | ||
|
||
implementation("com.amazonaws:aws-java-sdk-lambda:1.12.763") | ||
} | ||
|
||
verifyInstrumentation { | ||
// not using passesOnly to decrease the number of artifacts this is tested against | ||
passes 'com.amazonaws:aws-java-sdk-lambda:[1.11.280,)' | ||
} | ||
|
||
site { | ||
title 'AWS Lambda' | ||
type 'Framework' | ||
} |
29 changes: 29 additions & 0 deletions
29
...ain/java/com/agent/instrumentation/awsjavasdk1/services/lambda/FunctionProcessedData.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* | ||
* * Copyright 2024 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package com.agent.instrumentation.awsjavasdk1.services.lambda; | ||
|
||
/** | ||
* Function data extracted from the request and config. | ||
*/ | ||
class FunctionProcessedData { | ||
private final String functionName; | ||
private final String arn; | ||
|
||
public FunctionProcessedData(String functionName, String arn) { | ||
this.functionName = functionName; | ||
this.arn = arn; | ||
} | ||
|
||
public String getFunctionName() { | ||
return functionName; | ||
} | ||
|
||
public String getArn() { | ||
return arn; | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
.../src/main/java/com/agent/instrumentation/awsjavasdk1/services/lambda/FunctionRawData.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* | ||
* * Copyright 2024 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package com.agent.instrumentation.awsjavasdk1.services.lambda; | ||
|
||
import java.util.Objects; | ||
|
||
/** | ||
* Data necessary to calculate the ARN. This class is used as the key for the ARN cache. | ||
*/ | ||
public class FunctionRawData { | ||
private final String functionRef; | ||
private final String qualifier; | ||
private final String region; | ||
|
||
public FunctionRawData(String functionRef, String qualifier, String region) { | ||
this.functionRef = functionRef; | ||
this.qualifier = qualifier; | ||
this.region = region; | ||
} | ||
|
||
public String getFunctionRef() { | ||
return functionRef; | ||
} | ||
|
||
public String getQualifier() { | ||
return qualifier; | ||
} | ||
|
||
public String getRegion() { | ||
return region; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) { | ||
return true; | ||
} | ||
if (!(o instanceof FunctionRawData)) { | ||
return false; | ||
} | ||
FunctionRawData that = (FunctionRawData) o; | ||
return Objects.equals(functionRef, that.functionRef) && Objects.equals(qualifier, that.qualifier) && | ||
Objects.equals(region, that.region); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(functionRef, qualifier, region); | ||
} | ||
} |
124 changes: 124 additions & 0 deletions
124
...1.280/src/main/java/com/agent/instrumentation/awsjavasdk1/services/lambda/LambdaUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* | ||
* * Copyright 2024 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package com.agent.instrumentation.awsjavasdk1.services.lambda; | ||
|
||
import com.amazonaws.services.lambda.model.InvokeRequest; | ||
import com.newrelic.agent.bridge.AgentBridge; | ||
import com.newrelic.api.agent.CloudParameters; | ||
|
||
import java.util.function.Function; | ||
import java.util.regex.Pattern; | ||
|
||
public class LambdaUtil { | ||
|
||
private static final String PLATFORM = "aws_lambda"; | ||
private static final String NULL_ARN = ""; | ||
private static final String PREFIX = "arn:aws:lambda:"; | ||
private static final Function<FunctionRawData, FunctionProcessedData> CACHE = | ||
AgentBridge.collectionFactory.createAccessTimeBasedCache(3600, 8, LambdaUtil::processData); | ||
|
||
public static CloudParameters getCloudParameters(FunctionRawData functionRawData) { | ||
FunctionProcessedData data = CACHE.apply(functionRawData); | ||
String arn = data.getArn(); | ||
CloudParameters.ResourceIdParameter cloudParameters = CloudParameters.provider(PLATFORM); | ||
// the cache will always return the NULL_ARN when it is not possible to calculate the ARN | ||
// so saving a few cycles by using != instead of equals. | ||
if (arn != NULL_ARN) { | ||
cloudParameters.resourceId(arn); | ||
} | ||
|
||
return cloudParameters.build(); | ||
} | ||
|
||
/** | ||
* <p> | ||
* Calculates the simple function name and ARN given | ||
* the function name, qualifier, and possibly region (provided by config). | ||
* </p> | ||
* <p> | ||
* Aliases are returned as part of the ARN, but versions are removed | ||
* because they would make it harder to link to Lambdas/Alias entities. | ||
* </p> | ||
* <p> | ||
* If qualifiers are provided both in the function ref, and as a qualifier, the one in function ref "wins". | ||
* If they differ, the LambdaClient will throw an exception. | ||
* </p> | ||
* | ||
* @return a FunctionProcessedData object with the function name and ARN. | ||
* If any of its values cannot be calculated, it will be the NULL_ARN. | ||
*/ | ||
// Visible for testing | ||
static FunctionProcessedData processData(FunctionRawData data) { | ||
String functionRef = data.getFunctionRef(); | ||
|
||
String[] parts = functionRef.split(":"); | ||
|
||
String functionName = NULL_ARN; | ||
String arn = NULL_ARN; | ||
|
||
if (parts.length == 1) { | ||
// function ref is only function name | ||
// does not have the account id, so cannot assemble the ARN. | ||
functionName = functionRef; | ||
} else if (parts.length == 2) { | ||
// function ref is only function name with alias/version | ||
// does not have the account id, so cannot assemble the ARN. | ||
functionName = parts[0]; | ||
} else if (parts.length == 3) { | ||
// partial ARN: {account-id}:function:{function-name} | ||
functionName = parts[2]; | ||
String qualifier = data.getQualifier(); | ||
if (qualifier == null || isVersion(qualifier)) { | ||
arn = PREFIX + data.getRegion() + ":" + functionRef; | ||
} else { | ||
arn = PREFIX + data.getRegion() + ":" + functionRef + ":" + qualifier; | ||
} | ||
} else if (parts.length == 4) { | ||
// partial ARN with qualifier: {account-id}:function:{function-name}:{qualifier} | ||
functionName = parts[2]; | ||
if (isVersion(parts[3])) { | ||
String partialArnWithoutVersion = functionRef.substring(0, functionRef.length() - parts[3].length() - 1); | ||
arn = PREFIX + data.getRegion() + ":" + partialArnWithoutVersion; | ||
} else { | ||
arn = PREFIX + data.getRegion() + ":" + functionRef; | ||
} | ||
} else if (parts.length == 7) { | ||
// full ARN: arn:aws:lambda:{region}:{account-id}:function:{function-name} | ||
functionName = parts[6]; | ||
String qualifier = data.getQualifier(); | ||
if (qualifier == null || isVersion(qualifier)) { | ||
arn = functionRef; | ||
} else { | ||
arn = functionRef + ":" + qualifier; | ||
} | ||
} else if (parts.length == 8) { | ||
// full ARN with qualifier: arn:aws:lambda:{region}:{account-id}:function:{function-name}:{qualifier} | ||
functionName = parts[6]; | ||
if (isVersion(parts[7])) { | ||
// removing the version from the end | ||
arn = functionRef.substring(0, functionRef.length() - parts[7].length() - 1); | ||
} else { | ||
arn = functionRef; | ||
} | ||
} | ||
// reference should be invalid if the number of parts do not match any of the expected cases | ||
|
||
return new FunctionProcessedData(functionName, arn); | ||
} | ||
|
||
|
||
private static final Pattern VERSION_PATTERN = Pattern.compile("^[0-9]+$"); | ||
|
||
static boolean isVersion(String str) { | ||
return VERSION_PATTERN.matcher(str).matches(); | ||
} | ||
|
||
public static String getSimpleFunctionName(FunctionRawData functionRawData) { | ||
return CACHE.apply(functionRawData).getFunctionName(); | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
...280/src/main/java/com/amazonaws/services/lambda/AWSLambdaAsyncClient_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* | ||
* * Copyright 2024 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package com.amazonaws.services.lambda; | ||
|
||
import com.agent.instrumentation.awsjavasdk1.services.lambda.FunctionRawData; | ||
import com.agent.instrumentation.awsjavasdk1.services.lambda.LambdaUtil; | ||
import com.amazonaws.handlers.AsyncHandler; | ||
import com.amazonaws.services.lambda.model.InvokeRequest; | ||
import com.amazonaws.services.lambda.model.InvokeResult; | ||
import com.newrelic.api.agent.CloudParameters; | ||
import com.newrelic.api.agent.NewRelic; | ||
import com.newrelic.api.agent.Segment; | ||
import com.newrelic.api.agent.weaver.MatchType; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
|
||
import java.util.concurrent.Future; | ||
|
||
@Weave(type = MatchType.ExactClass, originalName = "com.amazonaws.services.lambda.AWSLambdaAsyncClient") | ||
public abstract class AWSLambdaAsyncClient_Instrumentation { | ||
|
||
protected abstract String getSigningRegion(); | ||
|
||
public Future<InvokeResult> invokeAsync(final InvokeRequest request, AsyncHandler<InvokeRequest, InvokeResult> asyncHandler) { | ||
FunctionRawData functionRawData = new FunctionRawData(request.getFunctionName(), request.getQualifier(), getSigningRegion()); | ||
CloudParameters cloudParameters = LambdaUtil.getCloudParameters(functionRawData); | ||
String functionName = LambdaUtil.getSimpleFunctionName(functionRawData); | ||
Segment segment = NewRelic.getAgent().getTransaction().startSegment("Lambda", "invoke/" + functionName); | ||
|
||
try { | ||
segment.reportAsExternal(cloudParameters); | ||
asyncHandler = new SegmentEndingAsyncHandler(asyncHandler, segment); | ||
return Weaver.callOriginal(); | ||
} catch (Throwable t) { | ||
segment.end(); | ||
throw t; | ||
} | ||
} | ||
|
||
private static class SegmentEndingAsyncHandler implements AsyncHandler<InvokeRequest, InvokeResult> { | ||
private final AsyncHandler<InvokeRequest, InvokeResult> originalHandler; | ||
private final Segment segment; | ||
|
||
public SegmentEndingAsyncHandler( | ||
AsyncHandler<InvokeRequest, InvokeResult> asyncHandler, Segment segment) { | ||
this.segment = segment; | ||
this.originalHandler = asyncHandler; | ||
} | ||
|
||
@Override | ||
public void onError(Exception exception) { | ||
segment.end(); | ||
if (originalHandler != null) { | ||
originalHandler.onError(exception); | ||
} | ||
} | ||
|
||
@Override | ||
public void onSuccess(InvokeRequest request, InvokeResult invokeResult) { | ||
segment.end(); | ||
if (originalHandler != null) { | ||
originalHandler.onSuccess(request, invokeResult); | ||
} | ||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...1.11.280/src/main/java/com/amazonaws/services/lambda/AWSLambdaClient_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* | ||
* * Copyright 2024 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package com.amazonaws.services.lambda; | ||
|
||
import com.agent.instrumentation.awsjavasdk1.services.lambda.FunctionRawData; | ||
import com.agent.instrumentation.awsjavasdk1.services.lambda.LambdaUtil; | ||
import com.amazonaws.services.lambda.model.InvokeRequest; | ||
import com.amazonaws.services.lambda.model.InvokeResult; | ||
import com.newrelic.api.agent.CloudParameters; | ||
import com.newrelic.api.agent.NewRelic; | ||
import com.newrelic.api.agent.Trace; | ||
import com.newrelic.api.agent.TracedMethod; | ||
import com.newrelic.api.agent.weaver.CatchAndLog; | ||
import com.newrelic.api.agent.weaver.MatchType; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
|
||
@Weave(type = MatchType.ExactClass, originalName = "com.amazonaws.services.lambda.AWSLambdaClient") | ||
public abstract class AWSLambdaClient_Instrumentation { | ||
|
||
abstract protected String getSigningRegion(); | ||
|
||
@Trace(leaf = true) | ||
public InvokeResult invoke(InvokeRequest invokeRequest) { | ||
FunctionRawData functionRawData = new FunctionRawData(invokeRequest.getFunctionName(), invokeRequest.getQualifier(), getSigningRegion()); | ||
CloudParameters cloudParameters = LambdaUtil.getCloudParameters(functionRawData); | ||
TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod(); | ||
tracedMethod.reportAsExternal(cloudParameters); | ||
tracedMethod.setMetricName("Lambda", "invoke", LambdaUtil.getSimpleFunctionName(functionRawData)); | ||
return Weaver.callOriginal(); | ||
} | ||
|
||
} | ||
|
Oops, something went wrong.