Skip to content

Commit cd43a9c

Browse files
committed
feat: Add auto-instrumentation support for Step Functions, SNS, SecretsManager, and Lambda
1 parent 4b13e06 commit cd43a9c

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-0
lines changed

awsagentprovider/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ dependencies {
3636
implementation("io.opentelemetry.contrib:opentelemetry-aws-resources")
3737
// Json file reader
3838
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
39+
// Import AWS SDK v1 core for ARN parsing utilities
40+
implementation("com.amazonaws:aws-java-sdk-core")
3941
// Export configuration
4042
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
43+
// SDK Lambda for Lambda ARN retrieval
44+
implementation("com.amazonaws:aws-java-sdk-lambda")
4145

4246
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
4347
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ private AwsAttributeKeys() {}
3838
static final AttributeKey<String> AWS_REMOTE_RESOURCE_IDENTIFIER =
3939
AttributeKey.stringKey("aws.remote.resource.identifier");
4040

41+
static final AttributeKey<String> AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER =
42+
AttributeKey.stringKey("aws.remote.resource.cfn.primary.identifier");
43+
4144
static final AttributeKey<String> AWS_REMOTE_RESOURCE_TYPE =
4245
AttributeKey.stringKey("aws.remote.resource.type");
4346

@@ -50,6 +53,23 @@ private AwsAttributeKeys() {}
5053
static final AttributeKey<String> AWS_CONSUMER_PARENT_SPAN_KIND =
5154
AttributeKey.stringKey("aws.consumer.parent.span.kind");
5255

56+
static final AttributeKey<String> AWS_STATE_MACHINE_ARN =
57+
AttributeKey.stringKey("aws.stepfunctions.state_machine.arn");
58+
59+
static final AttributeKey<String> AWS_STEP_FUNCTIONS_ACTIVITY_ARN =
60+
AttributeKey.stringKey("aws.stepfunctions.activity.arn");
61+
62+
static final AttributeKey<String> AWS_SNS_TOPIC_ARN = AttributeKey.stringKey("aws.sns.topic.arn");
63+
64+
static final AttributeKey<String> AWS_SECRET_ARN =
65+
AttributeKey.stringKey("aws.secretsmanager.secret.arn");
66+
67+
static final AttributeKey<String> AWS_LAMBDA_NAME =
68+
AttributeKey.stringKey("aws.lambda.function.name");
69+
70+
static final AttributeKey<String> AWS_LAMBDA_RESOURCE_ID =
71+
AttributeKey.stringKey("aws.lambda.resource_mapping.id");
72+
5373
// use the same AWS Resource attribute name defined by OTel java auto-instr for aws_sdk_v_1_1
5474
// TODO: all AWS specific attributes should be defined in semconv package and reused cross all
5575
// otel packages. Related sim -

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@
4343
import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT;
4444
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_AGENT_ID;
4545
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_BUCKET_NAME;
46+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER;
4647
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_DATA_SOURCE_ID;
4748
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_GUARDRAIL_ID;
4849
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_KNOWLEDGE_BASE_ID;
50+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_NAME;
51+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_RESOURCE_ID;
4952
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
5053
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_SERVICE;
5154
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_QUEUE_NAME;
@@ -55,7 +58,11 @@
5558
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER;
5659
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE;
5760
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE;
61+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SECRET_ARN;
62+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SNS_TOPIC_ARN;
5863
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
64+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STATE_MACHINE_ARN;
65+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STEP_FUNCTIONS_ACTIVITY_ARN;
5966
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME;
6067
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME;
6168
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL;
@@ -69,6 +76,11 @@
6976
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isDBSpan;
7077
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.isKeyPresent;
7178

79+
import com.amazonaws.arn.Arn;
80+
import com.amazonaws.services.lambda.AWSLambda;
81+
import com.amazonaws.services.lambda.AWSLambdaClientBuilder;
82+
import com.amazonaws.services.lambda.model.GetFunctionRequest;
83+
import com.amazonaws.services.lambda.model.GetFunctionResult;
7284
import io.opentelemetry.api.common.AttributeKey;
7385
import io.opentelemetry.api.common.Attributes;
7486
import io.opentelemetry.api.common.AttributesBuilder;
@@ -113,6 +125,10 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
113125
private static final String NORMALIZED_SQS_SERVICE_NAME = "AWS::SQS";
114126
private static final String NORMALIZED_BEDROCK_SERVICE_NAME = "AWS::Bedrock";
115127
private static final String NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME = "AWS::BedrockRuntime";
128+
private static final String NORMALIZED_STEPFUNCTIONS_SERVICE_NAME = "AWS::StepFunctions";
129+
private static final String NORMALIZED_SNS_SERVICE_NAME = "AWS::SNS";
130+
private static final String NORMALIZED_SECRETSMANAGER_SERVICE_NAME = "AWS::SecretsManager";
131+
private static final String NORMALIZED_LAMBDA_SERVICE_NAME = "AWS::Lambda";
116132

117133
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
118134
private static final String GRAPHQL = "graphql";
@@ -384,6 +400,18 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa
384400
case "AmazonBedrockRuntime": // AWS SDK v1
385401
case "BedrockRuntime": // AWS SDK v2
386402
return NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME;
403+
case "AWSStepFunctions": // AWS SDK v1
404+
case "Sfn": // AWS SDK v2
405+
return NORMALIZED_STEPFUNCTIONS_SERVICE_NAME;
406+
case "AmazonSNS":
407+
case "Sns":
408+
return NORMALIZED_SNS_SERVICE_NAME;
409+
case "AWSSecretsManager": // AWS SDK v1
410+
case "SecretsManager": // AWS SDK v2
411+
return NORMALIZED_SECRETSMANAGER_SERVICE_NAME;
412+
case "AWSLambda": // AWS SDK v1
413+
case "Lambda": // AWS SDK v2
414+
return NORMALIZED_LAMBDA_SERVICE_NAME;
387415
default:
388416
return "AWS::" + serviceName;
389417
}
@@ -405,6 +433,7 @@ private static String normalizeRemoteServiceName(SpanData span, String serviceNa
405433
private static void setRemoteResourceTypeAndIdentifier(SpanData span, AttributesBuilder builder) {
406434
Optional<String> remoteResourceType = Optional.empty();
407435
Optional<String> remoteResourceIdentifier = Optional.empty();
436+
Optional<String> cloudformationPrimaryIdentifier = Optional.empty();
408437

409438
if (isAwsSDKSpan(span)) {
410439
if (isKeyPresent(span, AWS_TABLE_NAME)) {
@@ -447,6 +476,50 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
447476
remoteResourceType = Optional.of(NORMALIZED_BEDROCK_SERVICE_NAME + "::Model");
448477
remoteResourceIdentifier =
449478
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(GEN_AI_REQUEST_MODEL)));
479+
} else if (isKeyPresent(span, AWS_STATE_MACHINE_ARN)) {
480+
remoteResourceType = Optional.of(NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + "::StateMachine");
481+
remoteResourceIdentifier =
482+
getSfnResourceNameFromArn(
483+
Optional.ofNullable(
484+
escapeDelimiters(span.getAttributes().get(AWS_STATE_MACHINE_ARN))));
485+
cloudformationPrimaryIdentifier =
486+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_STATE_MACHINE_ARN)));
487+
} else if (isKeyPresent(span, AWS_STEP_FUNCTIONS_ACTIVITY_ARN)) {
488+
remoteResourceType = Optional.of(NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + "::Activity");
489+
remoteResourceIdentifier =
490+
getSfnResourceNameFromArn(
491+
Optional.ofNullable(
492+
escapeDelimiters(span.getAttributes().get(AWS_STEP_FUNCTIONS_ACTIVITY_ARN))));
493+
cloudformationPrimaryIdentifier =
494+
Optional.ofNullable(
495+
escapeDelimiters(span.getAttributes().get(AWS_STEP_FUNCTIONS_ACTIVITY_ARN)));
496+
} else if (isKeyPresent(span, AWS_SNS_TOPIC_ARN)) {
497+
remoteResourceType = Optional.of(NORMALIZED_SNS_SERVICE_NAME + "::Topic");
498+
remoteResourceIdentifier =
499+
getSnsResourceNameFromArn(
500+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SNS_TOPIC_ARN))));
501+
cloudformationPrimaryIdentifier =
502+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SNS_TOPIC_ARN)));
503+
} else if (isKeyPresent(span, AWS_SECRET_ARN)) {
504+
remoteResourceType = Optional.of(NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret");
505+
remoteResourceIdentifier =
506+
getSecretsManagerResourceNameFromArn(
507+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN))));
508+
cloudformationPrimaryIdentifier =
509+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_SECRET_ARN)));
510+
} else if (isKeyPresent(span, AWS_LAMBDA_NAME)) {
511+
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::Function");
512+
remoteResourceIdentifier =
513+
getLambdaNameFromArbitraryName(
514+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME))));
515+
cloudformationPrimaryIdentifier =
516+
getLambdaArnFromArbitraryName(
517+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_NAME))));
518+
} else if (isKeyPresent(span, AWS_LAMBDA_RESOURCE_ID)) {
519+
remoteResourceType = Optional.of(NORMALIZED_LAMBDA_SERVICE_NAME + "::EventSourceMapping");
520+
remoteResourceIdentifier =
521+
Optional.ofNullable(escapeDelimiters(span.getAttributes().get(AWS_LAMBDA_RESOURCE_ID)));
522+
cloudformationPrimaryIdentifier = remoteResourceIdentifier;
450523
}
451524
} else if (isDBSpan(span)) {
452525
remoteResourceType = Optional.of(DB_CONNECTION_RESOURCE_TYPE);
@@ -457,6 +530,41 @@ private static void setRemoteResourceTypeAndIdentifier(SpanData span, Attributes
457530
builder.put(AWS_REMOTE_RESOURCE_TYPE, remoteResourceType.get());
458531
builder.put(AWS_REMOTE_RESOURCE_IDENTIFIER, remoteResourceIdentifier.get());
459532
}
533+
if (cloudformationPrimaryIdentifier.isPresent()) {
534+
builder.put(AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, cloudformationPrimaryIdentifier.get());
535+
}
536+
}
537+
538+
private static Optional<String> getLambdaNameFromArbitraryName(Optional<String> arbitraryName) {
539+
AWSLambda lambdaClient = AWSLambdaClientBuilder.defaultClient();
540+
GetFunctionRequest getFunctionRequest =
541+
new GetFunctionRequest().withFunctionName(arbitraryName.get());
542+
GetFunctionResult getFunctionResult = lambdaClient.getFunction(getFunctionRequest);
543+
return Optional.of(getFunctionResult.getConfiguration().getFunctionName());
544+
}
545+
546+
// NOTE: "name" in this case can be either the lambda name or lambda arn
547+
private static Optional<String> getLambdaArnFromArbitraryName(Optional<String> arbitraryName) {
548+
AWSLambda lambdaClient = AWSLambdaClientBuilder.defaultClient();
549+
GetFunctionRequest getFunctionRequest =
550+
new GetFunctionRequest().withFunctionName(arbitraryName.get());
551+
GetFunctionResult getFunctionResult = lambdaClient.getFunction(getFunctionRequest);
552+
return Optional.of(getFunctionResult.getConfiguration().getFunctionArn());
553+
}
554+
555+
private static Optional<String> getSecretsManagerResourceNameFromArn(Optional<String> stringArn) {
556+
Arn resourceArn = Arn.fromString(stringArn.get());
557+
return Optional.of(resourceArn.getResource().toString().split(":")[1]);
558+
}
559+
560+
private static Optional<String> getSfnResourceNameFromArn(Optional<String> stringArn) {
561+
Arn resourceArn = Arn.fromString(stringArn.get());
562+
return Optional.of(resourceArn.getResource().toString().split(":")[1]);
563+
}
564+
565+
private static Optional<String> getSnsResourceNameFromArn(Optional<String> stringArn) {
566+
Arn resourceArn = Arn.fromString(stringArn.get());
567+
return Optional.of(resourceArn.getResource().toString());
460568
}
461569

462570
/**

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@
3535
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_IDENTIFIER;
3636
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_RESOURCE_TYPE;
3737
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_REMOTE_SERVICE;
38+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SECRET_ARN;
39+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SNS_TOPIC_ARN;
3840
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
41+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STATE_MACHINE_ARN;
42+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STEP_FUNCTIONS_ACTIVITY_ARN;
3943
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME;
4044
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME;
4145
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL;
@@ -757,6 +761,32 @@ public void testSdkClientSpanWithRemoteResourceAttributes() {
757761
mockAttribute(GEN_AI_REQUEST_MODEL, "test.service_^id");
758762
validateRemoteResourceAttributes("AWS::Bedrock::Model", "test.service_^^id");
759763
mockAttribute(GEN_AI_REQUEST_MODEL, null);
764+
765+
// Validate behaviour of AWS_STATE_MACHINE_ARN attribute, then remove it.
766+
mockAttribute(
767+
AWS_STATE_MACHINE_ARN,
768+
"arn:aws:states:us-east-1:123456789012:stateMachine:test_state_machine");
769+
validateRemoteResourceAttributes("AWS::StepFunctions::StateMachine", "test_state_machine");
770+
mockAttribute(AWS_STATE_MACHINE_ARN, null);
771+
772+
// Validate behaviour of AWS_STEPFUNCTIONS_ACTIVITY_ARN, then remove it.
773+
mockAttribute(
774+
AWS_STEP_FUNCTIONS_ACTIVITY_ARN,
775+
"arn:aws:states:us-east-1:007003123456789012:activity:testActivity");
776+
validateRemoteResourceAttributes("AWS::StepFunctions::Activity", "testActivity");
777+
mockAttribute(AWS_STEP_FUNCTIONS_ACTIVITY_ARN, null);
778+
779+
// Validate behaviour of AWS_SNS_TOPIC_ARN, then remove it.
780+
mockAttribute(AWS_SNS_TOPIC_ARN, "arn:aws:sns:us-west-2:012345678901:testTopic");
781+
validateRemoteResourceAttributes("AWS::SNS::Topic", "testTopic");
782+
mockAttribute(AWS_SNS_TOPIC_ARN, null);
783+
784+
// Validate behaviour of AWS_SECRET_ARN, then remove it.
785+
mockAttribute(
786+
AWS_SECRET_ARN, "arn:aws:secretsmanager:us-east-1:123456789012:secret:secretName");
787+
validateRemoteResourceAttributes("AWS::SecretsManager::Secret", "secretName");
788+
mockAttribute(AWS_SECRET_ARN, null);
789+
760790
mockAttribute(RPC_SYSTEM, "null");
761791
}
762792

@@ -1162,6 +1192,7 @@ public void testNormalizeRemoteServiceName_AwsSdk() {
11621192
testAwsSdkServiceNormalization("AWSBedrockAgentRuntime", "AWS::Bedrock");
11631193
testAwsSdkServiceNormalization("AWSBedrockAgent", "AWS::Bedrock");
11641194
testAwsSdkServiceNormalization("AmazonBedrockRuntime", "AWS::BedrockRuntime");
1195+
testAwsSdkServiceNormalization("AWSStepFunctions", "AWS::StepFunctions");
11651196

11661197
// AWS SDK V2
11671198
testAwsSdkServiceNormalization("DynamoDb", "AWS::DynamoDB");

0 commit comments

Comments
 (0)