Skip to content

Commit 2dad73c

Browse files
authored
Fix: Lambda Topology Issue (#1016) (#1085)
## What does this pull request do? Cherry picks the Lambda Topology fix from our Lambda Layer v1 branch. Original PR: #1016 ## Test strategy Unit tests and followed e2e test strategy used in the [original PR](#1016) to sanity check correct behavior in service topology. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 284eed6 commit 2dad73c

File tree

4 files changed

+300
-5
lines changed

4 files changed

+300
-5
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
public final class AwsApplicationSignalsCustomizerProvider
7878
implements AutoConfigurationCustomizerProvider {
7979
static final String AWS_LAMBDA_FUNCTION_NAME_CONFIG = "AWS_LAMBDA_FUNCTION_NAME";
80+
static final String LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT =
81+
"LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT";
8082

8183
private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1);
8284
private static final Logger logger =

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: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,23 @@
4545
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_ADDRESS;
4646
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT;
4747
import static io.opentelemetry.semconv.SemanticAttributes.URL_FULL;
48+
import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT;
4849
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID;
4950
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME;
5051
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER;
5152
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID;
5253
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ARN;
5354
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID;
5455
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID;
56+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_ARN;
57+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME;
5558
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID;
5659
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
5760
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
5861
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
5962
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_URL;
6063
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_DB_USER;
64+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_ENVIRONMENT;
6165
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION;
6266
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER;
6367
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE;
@@ -129,6 +133,9 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
129133
private static final String NORMALIZED_SECRETSMANAGER_SERVICE_NAME = "AWS::SecretsManager";
130134
private static final String NORMALIZED_LAMBDA_SERVICE_NAME = "AWS::Lambda";
131135

136+
// Constants for Lambda operations
137+
private static final String LAMBDA_INVOKE_OPERATION = "Invoke";
138+
132139
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
133140
private static final String GRAPHQL = "graphql";
134141

@@ -167,6 +174,7 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re
167174
setService(resource, span, builder);
168175
setEgressOperation(span, builder);
169176
setRemoteServiceAndOperation(span, builder);
177+
setRemoteEnvironment(span, builder);
170178
setRemoteResourceTypeAndIdentifier(span, builder);
171179
setSpanKindForDependency(span, builder);
172180
setHttpStatus(span, builder);
@@ -291,6 +299,27 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde
291299
builder.put(AWS_REMOTE_OPERATION, remoteOperation);
292300
}
293301

302+
/**
303+
* Remote environment is used to identify the environment of downstream services. Currently only
304+
* set to "lambda:default" for Lambda Invoke operations when aws-api system is detected.
305+
*/
306+
private static void setRemoteEnvironment(SpanData span, AttributesBuilder builder) {
307+
// We want to treat downstream Lambdas as a service rather than a resource because
308+
// Application Signals topology map gets disconnected due to conflicting Lambda Entity
309+
// definitions
310+
// Additional context can be found in
311+
// https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
312+
if (isLambdaInvokeOperation(span)) {
313+
// TODO: This should be passed via ConfigProperties from
314+
// AwsApplicationSignalsCustomizerProvider
315+
String remoteEnvironment =
316+
Optional.ofNullable(System.getenv(LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT))
317+
.filter(s -> !s.isEmpty())
318+
.orElse("default");
319+
builder.put(AWS_REMOTE_ENVIRONMENT, "lambda:" + remoteEnvironment);
320+
}
321+
}
322+
294323
/**
295324
* When the remote call operation is undetermined for http use cases, will try to extract the
296325
* remote operation name from http url string
@@ -373,6 +402,15 @@ private static String generateRemoteService(SpanData span) {
373402
return remoteService;
374403
}
375404

405+
private static boolean isLambdaInvokeOperation(SpanData span) {
406+
if (!isAwsSDKSpan(span)) {
407+
return false;
408+
}
409+
String rpcService = getRemoteService(span, RPC_SERVICE);
410+
return ("AWSLambda".equals(rpcService) || "Lambda".equals(rpcService))
411+
&& LAMBDA_INVOKE_OPERATION.equals(span.getAttributes().get(RPC_METHOD));
412+
}
413+
376414
/**
377415
* If the span is an AWS SDK span, normalize the name to align with <a
378416
* href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS
@@ -420,7 +458,19 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa
420458
return NORMALIZED_SECRETSMANAGER_SERVICE_NAME;
421459
case "AWSLambda": // AWS SDK v1
422460
case "Lambda": // AWS SDK v2
423-
return NORMALIZED_LAMBDA_SERVICE_NAME;
461+
if (isLambdaInvokeOperation(span)) {
462+
// AWS_LAMBDA_NAME can contain either a function name or function ARN since Lambda AWS
463+
// SDK calls accept both formats
464+
Optional<String> lambdaFunctionName =
465+
getLambdaFunctionNameFromArn(
466+
Optional.ofNullable(span.getAttributes().get(AWS_LAMBDA_NAME)));
467+
// If Lambda name is not present, use UnknownRemoteService
468+
// This is intentional - we want to clearly indicate when the Lambda function name
469+
// is missing rather than falling back to a generic service name
470+
return lambdaFunctionName.orElse(UNKNOWN_REMOTE_SERVICE);
471+
} else {
472+
return NORMALIZED_LAMBDA_SERVICE_NAME;
473+
}
424474
default:
425475
return "AWS::" + serviceName;
426476
}
@@ -518,6 +568,19 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
518568
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN))));
519569
cloudformationPrimaryIdentifier =
520570
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN)));
571+
} else if (isKeyPresent(span, AWS_LAMBDA_NAME)) {
572+
// For non-Invoke Lambda operations, treat Lambda as a resource,
573+
// see normalizeRemoteServiceName for more information.
574+
if (!isLambdaInvokeOperation(span)) {
575+
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::Function");
576+
// AWS_LAMBDA_NAME can contain either a function name or function ARN since Lambda AWS SDK
577+
// calls accept both formats
578+
remoteResourceIdentifier =
579+
getLambdaFunctionNameFromArn(
580+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME))));
581+
cloudformationPrimaryIdentifier =
582+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_ARN)));
583+
}
521584
} else if (isKeyPresent(span, AWS_LAMBDA_RESOURCE_ID)) {
522585
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::EventSourceMapping");
523586
remoteResourceIdentifier =
@@ -539,6 +602,14 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
539602
}
540603
}
541604

605+
private static Optional<String> getLambdaFunctionNameFromArn(Optional<String> stringArn) {
606+
if (stringArn.isPresent() && stringArn.get().startsWith("arn:aws:lambda:")) {
607+
Arn resourceArn = Arn.fromString(stringArn.get());
608+
return Optional.of(resourceArn.getResource().toString().split(":")[1]);
609+
}
610+
return stringArn;
611+
}
612+
542613
private static Optional<String> getSecretsManagerResourceNameFromArn(Optional<String> stringArn) {
543614
Arn resourceArn = Arn.fromString(stringArn.get());
544615
return Optional.of(resourceArn.getResource().toString().split(":")[1]);

0 commit comments

Comments
 (0)