45
45
import static io .opentelemetry .semconv .SemanticAttributes .SERVER_SOCKET_ADDRESS ;
46
46
import static io .opentelemetry .semconv .SemanticAttributes .SERVER_SOCKET_PORT ;
47
47
import static io .opentelemetry .semconv .SemanticAttributes .URL_FULL ;
48
+ import static software .amazon .opentelemetry .javaagent .providers .AwsApplicationSignalsCustomizerProvider .LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT ;
48
49
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_AGENT_ID ;
49
50
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_BUCKET_NAME ;
50
51
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER ;
51
52
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_DATA_SOURCE_ID ;
52
53
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_GUARDRAIL_ARN ;
53
54
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_GUARDRAIL_ID ;
54
55
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 ;
55
58
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LAMBDA_RESOURCE_ID ;
56
59
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LOCAL_OPERATION ;
57
60
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_LOCAL_SERVICE ;
58
61
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_QUEUE_NAME ;
59
62
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_QUEUE_URL ;
60
63
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_REMOTE_DB_USER ;
64
+ import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_REMOTE_ENVIRONMENT ;
61
65
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_REMOTE_OPERATION ;
62
66
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_REMOTE_RESOURCE_IDENTIFIER ;
63
67
import static software .amazon .opentelemetry .javaagent .providers .AwsAttributeKeys .AWS_REMOTE_RESOURCE_TYPE ;
@@ -129,6 +133,9 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
129
133
private static final String NORMALIZED_SECRETSMANAGER_SERVICE_NAME = "AWS::SecretsManager" ;
130
134
private static final String NORMALIZED_LAMBDA_SERVICE_NAME = "AWS::Lambda" ;
131
135
136
+ // Constants for Lambda operations
137
+ private static final String LAMBDA_INVOKE_OPERATION = "Invoke" ;
138
+
132
139
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
133
140
private static final String GRAPHQL = "graphql" ;
134
141
@@ -167,6 +174,7 @@ private Attributes generateDependencyMetricAttributes(SpanData span, Resource re
167
174
setService (resource , span , builder );
168
175
setEgressOperation (span , builder );
169
176
setRemoteServiceAndOperation (span , builder );
177
+ setRemoteEnvironment (span , builder );
170
178
setRemoteResourceTypeAndIdentifier (span , builder );
171
179
setSpanKindForDependency (span , builder );
172
180
setHttpStatus (span , builder );
@@ -291,6 +299,27 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde
291
299
builder .put (AWS_REMOTE_OPERATION , remoteOperation );
292
300
}
293
301
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
+
294
323
/**
295
324
* When the remote call operation is undetermined for http use cases, will try to extract the
296
325
* remote operation name from http url string
@@ -373,6 +402,15 @@ private static String generateRemoteService(SpanData span) {
373
402
return remoteService ;
374
403
}
375
404
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
+
376
414
/**
377
415
* If the span is an AWS SDK span, normalize the name to align with <a
378
416
* 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
420
458
return NORMALIZED_SECRETSMANAGER_SERVICE_NAME ;
421
459
case "AWSLambda" : // AWS SDK v1
422
460
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
+ }
424
474
default :
425
475
return "AWS::" + serviceName ;
426
476
}
@@ -518,6 +568,19 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
518
568
Optional .ofNullable (escapeDelimiters (span .getAttributes ().get (AWS_SECRET_ARN ))));
519
569
cloudformationPrimaryIdentifier =
520
570
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
+ }
521
584
} else if (isKeyPresent (span , AWS_LAMBDA_RESOURCE_ID )) {
522
585
remoteResourceType = Optional .of (NORMALIZED_LAMBDA_SERVICE_NAME + "::EventSourceMapping" );
523
586
remoteResourceIdentifier =
@@ -539,6 +602,14 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
539
602
}
540
603
}
541
604
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
+
542
613
private static Optional <String > getSecretsManagerResourceNameFromArn (Optional <String > stringArn ) {
543
614
Arn resourceArn = Arn .fromString (stringArn .get ());
544
615
return Optional .of (resourceArn .getResource ().toString ().split (":" )[1 ]);
0 commit comments