Skip to content

Commit cd3c2c5

Browse files
authored
Fix: Lambda Topology Issue (#1016)
**Issue #, if available:** Lambda Topology issue -- more context in PRs for Python and JavaScript: - aws-observability/aws-otel-python-instrumentation#319 - aws-observability/aws-otel-js-instrumentation#149 **Description of changes:** - Apply fix for the Lambda Topology issue. The logic mimics the fix in our other ADOT SDKs. - Adding back AWS Resource support for Lambda. - #907 - We previously removed support due to the Lambda Topology issue **Test plan:** Set up two Lambda functions with Java runtimes and tested with custom Lambda layer with fix built-in. Tested both AWS SDK v1 and v2. Below are screenshots of the topology for various configurations. **v1 Topology (lambdaA & lambdaB instrumented)** <img width="1311" alt="Screenshot 2025-02-07 at 11 48 51 AM" src="https://github.com/user-attachments/assets/48234604-ae4b-49cd-926f-05cdd74038a7" /> **v2 Topology (lambdaA & lambdaB instrumented)** <img width="1222" alt="Screenshot 2025-02-07 at 11 26 34 AM" src="https://github.com/user-attachments/assets/cf7446f3-888f-4756-8ce0-e5ed1e97c9b5" /> We observe the following correct behaviors for topology above: - Service entity node for `Invoke` call to downstream lambda. - AWS Resource node for `GetFunction` call to downstream lambda. - AWS Resource node for `ListBuckets` call to downstream s3. **v1 Topology (lambdaB not instrumented)** <img width="965" alt="Screenshot 2025-02-07 at 12 17 59 PM" src="https://github.com/user-attachments/assets/67c361c0-4b8b-4d54-b1dd-0f21a9eee6ff" /> **v2 Topology (lambdaB not instrumented)** <img width="965" alt="Screenshot 2025-02-07 at 12 17 59 PM" src="https://github.com/user-attachments/assets/67c361c0-4b8b-4d54-b1dd-0f21a9eee6ff" /> We observe the following correct behaviors for topology above: - Downstream lambda called with `Invoke` is correctly treated as RemoteService entity when not instrumented Additionally, I generated the spans locally to verify the lambda instrumentation patch behaves correctly. **v1 Invoke** <img width="1281" alt="Screenshot 2025-02-06 at 10 10 23 PM" src="https://github.com/user-attachments/assets/8d025453-4658-47c7-8c50-261be8b665f5" /> **v2 Invoke** <img width="1281" alt="Screenshot 2025-02-06 at 10 11 49 PM" src="https://github.com/user-attachments/assets/46b382d0-9475-4871-9773-ed78e609d4a2" /> **v1 GetFunction** <img width="1281" alt="Screenshot 2025-02-06 at 10 08 53 PM" src="https://github.com/user-attachments/assets/a59e2de6-d50c-47bf-b9d6-171c1ce7cc02" /> **v2 GetFunction** <img width="1281" alt="Screenshot 2025-02-06 at 10 10 05 PM" src="https://github.com/user-attachments/assets/bec349e0-92c7-4d80-b778-8fa29f3b1ab2" /> We observe the following correct behaviors in the spans above: - For `Invoke` calls, we see `aws.remote.service` and `aws.remote.environment` correctly populated in the spans. - For non-`Invoke` calls (i.e. `GetFunction`), we see AWS Resource attributes such as `aws.remote.resource.identifier` and `aws.cloudformation.primary.identifier` correctly populated. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent a2b1aac commit cd3c2c5

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeKeys.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ private AwsAttributeKeys() {}
3232
static final AttributeKey<String> AWS_REMOTE_SERVICE =
3333
AttributeKey.stringKey("aws.remote.service");
3434

35+
static final AttributeKey<String> AWS_REMOTE_ENVIRONMENT =
36+
AttributeKey.stringKey("aws.remote.environment");
37+
3538
static final AttributeKey<String> AWS_REMOTE_OPERATION =
3639
AttributeKey.stringKey("aws.remote.operation");
3740

@@ -64,6 +67,9 @@ private AwsAttributeKeys() {}
6467
static final AttributeKey<String> AWS_SECRET_ARN =
6568
AttributeKey.stringKey("aws.secretsmanager.secret.arn");
6669

70+
static final AttributeKey<String> AWS_LAMBDA_NAME =
71+
AttributeKey.stringKey("aws.lambda.function.name");
72+
6773
static final AttributeKey<String> AWS_LAMBDA_ARN =
6874
AttributeKey.stringKey("aws.lambda.function.arn");
6975

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@
5252
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ARN;
5353
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID;
5454
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID;
55+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_ARN;
56+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME;
5557
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID;
5658
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
5759
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
5860
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
5961
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_URL;
6062
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_DB_USER;
63+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_ENVIRONMENT;
6164
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION;
6265
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER;
6366
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE;
@@ -518,6 +521,37 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
518521
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN))));
519522
cloudformationPrimaryIdentifier =
520523
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN)));
524+
} else if (isKeyPresent(span, AWS_LAMBDA_NAME)) {
525+
// Handling downstream Lambda as a service vs. an AWS resource:
526+
// - If the method call is "Invoke", we treat downstream Lambda as a service.
527+
// - Otherwise, we treat it as an AWS resource.
528+
//
529+
// This addresses a Lambda topology issue in Application Signals.
530+
// More context in PR:
531+
// https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
532+
//
533+
// NOTE: The environment variables LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was
534+
// introduced as part of this fix.
535+
// It is optional and allows users to override the default value if needed.
536+
if ("Invoke".equals(getRemoteOperation(span, RPC_METHOD))) {
537+
Optional<String> remoteService =
538+
getLambdaFunctionNameFromArn(
539+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME))));
540+
builder.put(AWS_REMOTE_SERVICE, remoteService.get());
541+
542+
String remoteEnvironment =
543+
Optional.ofNullable(System.getenv("LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT"))
544+
.filter(s -> !s.isEmpty())
545+
.orElse("default");
546+
builder.put(AWS_REMOTE_ENVIRONMENT, "lambda:" + remoteEnvironment);
547+
} else {
548+
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::Function");
549+
remoteResourceIdentifier =
550+
getLambdaFunctionNameFromArn(
551+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME))));
552+
cloudformationPrimaryIdentifier =
553+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_ARN)));
554+
}
521555
} else if (isKeyPresent(span, AWS_LAMBDA_RESOURCE_ID)) {
522556
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::EventSourceMapping");
523557
remoteResourceIdentifier =
@@ -539,6 +573,14 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
539573
}
540574
}
541575

576+
private static Optional<String> getLambdaFunctionNameFromArn(Optional<String> stringArn) {
577+
if (stringArn.isPresent() && stringArn.get().startsWith("arn:aws:lambda:")) {
578+
Arn resourceArn = Arn.fromString(stringArn.get());
579+
return Optional.of(resourceArn.getResource().toString().split(":")[1]);
580+
}
581+
return stringArn;
582+
}
583+
542584
private static Optional<String> getSecretsManagerResourceNameFromArn(Optional<String> stringArn) {
543585
Arn resourceArn = Arn.fromString(stringArn.get());
544586
return Optional.of(resourceArn.getResource().toString().split(":")[1]);

awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGeneratorTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID;
2727
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID;
2828
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID;
29+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME;
2930
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID;
3031
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
3132
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
@@ -874,6 +875,11 @@ public void testSdkClientSpanWithRemoteResourceAttributes() {
874875
validateRemoteResourceAttributes("AWS::SecretsManager::Secret", "secretName");
875876
mockAttribute(AWS_SECRET_ARN, null);
876877

878+
// Validate behaviour of AWS_LAMBDA_NAME, then remove it.
879+
mockAttribute(AWS_LAMBDA_NAME, "testLambdaName");
880+
validateRemoteResourceAttributes("AWS::Lambda::Function", "testLambdaName");
881+
mockAttribute(AWS_LAMBDA_NAME, null);
882+
877883
// Validate behaviour of AWS_LAMBDA_RESOURCE_ID
878884
mockAttribute(AWS_LAMBDA_RESOURCE_ID, "eventSourceId");
879885
validateRemoteResourceAttributes("AWS::Lambda::EventSourceMapping", "eventSourceId");

0 commit comments

Comments
 (0)