Skip to content

Commit d4ac453

Browse files
committed
address pr comments
1 parent 1c78149 commit d4ac453

File tree

2 files changed

+148
-23
lines changed

2 files changed

+148
-23
lines changed

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

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re
170170
setService(resource, span, builder);
171171
setEgressOperation(span, builder);
172172
setRemoteServiceAndOperation(span, builder);
173+
setRemoteEnvironment(span, builder);
173174
setRemoteResourceTypeAndIdentifier(span, builder);
174175
setSpanKindForDependency(span, builder);
175176
setHttpStatus(span, builder);
@@ -259,6 +260,29 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde
259260
remoteService = normalizeRemoteServiceName(span, getRemoteService(span, RPC_SERVICE));
260261
remoteOperation = getRemoteOperation(span, RPC_METHOD);
261262

263+
// Handling downstream Lambda as a service vs. an AWS resource:
264+
// - If the method call is "Invoke", we treat downstream Lambda as a service.
265+
// - Otherwise, we treat it as an AWS resource.
266+
//
267+
// This addresses a Lambda topology issue in Application Signals.
268+
// More context in PR:
269+
// https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
270+
//
271+
// NOTE: The environment variable LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was
272+
// introduced as part of this fix.
273+
// It is optional and allows users to override the default value if needed.
274+
if (isAwsSDKSpan(span)
275+
&& isKeyPresent(span, AWS_LAMBDA_NAME)
276+
&& "Invoke".equals(remoteOperation)) {
277+
// When invoking Lambda, treat it as a service rather than a resource
278+
Optional<String> lambdaFunctionName =
279+
getLambdaFunctionNameFromArn(
280+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME))));
281+
if (lambdaFunctionName.isPresent()) {
282+
remoteService = lambdaFunctionName.get();
283+
}
284+
}
285+
262286
} else if (isDBSpan(span)) {
263287
remoteService = getRemoteService(span, DB_SYSTEM);
264288
if (isKeyPresent(span, DB_OPERATION)) {
@@ -294,6 +318,30 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde
294318
builder.put(AWS_REMOTE_OPERATION, remoteOperation);
295319
}
296320

321+
/**
322+
* Sets the remote environment attribute for special service cases. Currently handles Lambda
323+
* invoke operations where the downstream Lambda is treated as a service.
324+
*
325+
* @param span Span data
326+
* @param builder Span attributes builder
327+
*/
328+
private static void setRemoteEnvironment(SpanData span, AttributesBuilder builder) {
329+
// Check if this is a Lambda Invoke operation
330+
if (isAwsSDKSpan(span)
331+
&& isKeyPresent(span, AWS_LAMBDA_NAME)
332+
&& "Invoke".equals(span.getAttributes().get(RPC_METHOD))) {
333+
// Get the remote environment configuration value
334+
// TODO: This should be passed via ConfigProperties from
335+
// AwsApplicationSignalsCustomizerProvider
336+
// For now, using System.getenv as a temporary solution
337+
String remoteEnvironment =
338+
Optional.ofNullable(System.getenv("LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT"))
339+
.filter(s -> !s.isEmpty())
340+
.orElse("default");
341+
builder.put(AWS_REMOTE_ENVIRONMENT, "lambda:" + remoteEnvironment);
342+
}
343+
}
344+
297345
/**
298346
* When the remote call operation is undetermined for http use cases, will try to extract the
299347
* remote operation name from http url string
@@ -522,29 +570,9 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
522570
cloudformationPrimaryIdentifier =
523571
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN)));
524572
} 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 {
573+
// For non-Invoke Lambda operations, treat as a resource
574+
// Invoke operations are handled in setRemoteServiceAndOperation
575+
if (!"Invoke".equals(span.getAttributes().get(RPC_METHOD))) {
548576
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::Function");
549577
remoteResourceIdentifier =
550578
getLambdaFunctionNameFromArn(

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@
2323
import static org.mockito.Mockito.when;
2424
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID;
2525
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME;
26+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER;
2627
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID;
2728
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID;
2829
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID;
30+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_ARN;
2931
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME;
3032
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID;
3133
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
3234
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
3335
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
3436
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_URL;
3537
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_DB_USER;
38+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_ENVIRONMENT;
3639
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_OPERATION;
3740
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER;
3841
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE;
@@ -751,6 +754,86 @@ public void testPeerServiceDoesNotOverrideAwsRemoteService() {
751754
assertThat(actualAttributes.get(AWS_REMOTE_SERVICE)).isEqualTo("TestString");
752755
}
753756

757+
@Test
758+
public void testLambdaInvokeSpanWithRemoteServiceAttributes() {
759+
mockAttribute(RPC_SYSTEM, "aws-api");
760+
mockAttribute(RPC_METHOD, "Invoke");
761+
mockAttribute(AWS_LAMBDA_NAME, "myLambdaFunction");
762+
763+
when(spanDataMock.getKind()).thenReturn(SpanKind.CLIENT);
764+
Attributes actualAttributes =
765+
GENERATOR.generateMetricAttributeMapFromSpan(spanDataMock, resource).get(DEPENDENCY_METRIC);
766+
767+
// When RPC_METHOD is "Invoke", Lambda should be treated as a service
768+
assertThat(actualAttributes.get(AWS_REMOTE_SERVICE)).isEqualTo("myLambdaFunction");
769+
assertThat(actualAttributes.get(AWS_REMOTE_ENVIRONMENT)).isEqualTo("lambda:default");
770+
// Should not set resource attributes when treated as a service
771+
assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_TYPE)).isNull();
772+
assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)).isNull();
773+
774+
mockAttribute(RPC_METHOD, null);
775+
mockAttribute(AWS_LAMBDA_NAME, null);
776+
}
777+
778+
@Test
779+
public void testLambdaInvokeSpanWithLambdaArnInsteadOfName() {
780+
mockAttribute(RPC_SYSTEM, "aws-api");
781+
mockAttribute(RPC_METHOD, "Invoke");
782+
mockAttribute(
783+
AWS_LAMBDA_NAME, "arn:aws:lambda:us-east-1:123456789012:function:myLambdaFunction");
784+
785+
when(spanDataMock.getKind()).thenReturn(SpanKind.CLIENT);
786+
Attributes actualAttributes =
787+
GENERATOR.generateMetricAttributeMapFromSpan(spanDataMock, resource).get(DEPENDENCY_METRIC);
788+
789+
// Should extract function name from ARN
790+
assertThat(actualAttributes.get(AWS_REMOTE_SERVICE)).isEqualTo("myLambdaFunction");
791+
assertThat(actualAttributes.get(AWS_REMOTE_ENVIRONMENT)).isEqualTo("lambda:default");
792+
793+
mockAttribute(RPC_METHOD, null);
794+
mockAttribute(AWS_LAMBDA_NAME, null);
795+
}
796+
797+
@Test
798+
public void testLambdaNonInvokeSpanWithRemoteResourceAttributes() {
799+
mockAttribute(RPC_SYSTEM, "aws-api");
800+
mockAttribute(RPC_METHOD, "GetFunction");
801+
mockAttribute(AWS_LAMBDA_NAME, "myLambdaFunction");
802+
mockAttribute(
803+
AWS_LAMBDA_ARN, "arn:aws:lambda:us-east-1:123456789012:function:myLambdaFunction");
804+
805+
// When RPC_METHOD is not "Invoke", Lambda should be treated as a resource
806+
validateRemoteResourceAttributes("AWS::Lambda::Function", "myLambdaFunction");
807+
808+
// Also verify cloudformation primary identifier is set to ARN
809+
when(spanDataMock.getKind()).thenReturn(SpanKind.CLIENT);
810+
Attributes actualAttributes =
811+
GENERATOR.generateMetricAttributeMapFromSpan(spanDataMock, resource).get(DEPENDENCY_METRIC);
812+
assertThat(actualAttributes.get(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER))
813+
.isEqualTo("arn:aws:lambda:us-east-1:123456789012:function:myLambdaFunction");
814+
815+
mockAttribute(RPC_METHOD, null);
816+
mockAttribute(AWS_LAMBDA_NAME, null);
817+
mockAttribute(AWS_LAMBDA_ARN, null);
818+
}
819+
820+
@Test
821+
public void testLambdaNonInvokeSpanWithLambdaArnInsteadOfName() {
822+
mockAttribute(RPC_SYSTEM, "aws-api");
823+
mockAttribute(RPC_METHOD, "ListFunctions");
824+
mockAttribute(
825+
AWS_LAMBDA_NAME, "arn:aws:lambda:us-east-1:123456789012:function:myLambdaFunction");
826+
mockAttribute(
827+
AWS_LAMBDA_ARN, "arn:aws:lambda:us-east-1:123456789012:function:myLambdaFunction");
828+
829+
// Should extract function name from ARN
830+
validateRemoteResourceAttributes("AWS::Lambda::Function", "myLambdaFunction");
831+
832+
mockAttribute(RPC_METHOD, null);
833+
mockAttribute(AWS_LAMBDA_NAME, null);
834+
mockAttribute(AWS_LAMBDA_ARN, null);
835+
}
836+
754837
@Test
755838
public void testSdkClientSpanWithRemoteResourceAttributes() {
756839
mockAttribute(RPC_SYSTEM, "aws-api");
@@ -881,6 +964,20 @@ public void testSdkClientSpanWithRemoteResourceAttributes() {
881964
validateRemoteResourceAttributes("AWS::Lambda::Function", "testLambdaName");
882965
mockAttribute(AWS_LAMBDA_NAME, null);
883966

967+
// Validate that Lambda Invoke does NOT set resource attributes
968+
mockAttribute(RPC_METHOD, "Invoke");
969+
mockAttribute(AWS_LAMBDA_NAME, "testLambdaName");
970+
when(spanDataMock.getKind()).thenReturn(SpanKind.CLIENT);
971+
Attributes actualAttributes =
972+
GENERATOR.generateMetricAttributeMapFromSpan(spanDataMock, resource).get(DEPENDENCY_METRIC);
973+
// Lambda Invoke should be treated as a service, not a resource
974+
assertThat(actualAttributes.get(AWS_REMOTE_SERVICE)).isEqualTo("testLambdaName");
975+
assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_TYPE)).isNull();
976+
assertThat(actualAttributes.get(AWS_REMOTE_RESOURCE_IDENTIFIER)).isNull();
977+
assertThat(actualAttributes.get(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER)).isNull();
978+
mockAttribute(RPC_METHOD, null);
979+
mockAttribute(AWS_LAMBDA_NAME, null);
980+
884981
// Validate behaviour of AWS_LAMBDA_RESOURCE_ID
885982
mockAttribute(AWS_LAMBDA_RESOURCE_ID, "eventSourceId");
886983
validateRemoteResourceAttributes("AWS::Lambda::EventSourceMapping", "eventSourceId");

0 commit comments

Comments
 (0)