Skip to content

Commit 974ef1a

Browse files
authored
Fix CFN Identifier bug for Bedrock and Lambda (#220)
1 parent deaaf57 commit 974ef1a

File tree

8 files changed

+75
-23
lines changed

8 files changed

+75
-23
lines changed

src/AWS.Distro.OpenTelemetry.AutoInstrumentation/AwsAttributeKeys.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ internal sealed class AwsAttributeKeys
5050
internal static readonly string AttributeAWSDynamoTableName = "aws.table_name";
5151
internal static readonly string AttributeAWSSQSQueueUrl = "aws.queue_url";
5252

53+
internal static readonly string AttributeAWSLambdaFunctionArn = "aws.lambda.function.arn";
5354
internal static readonly string AttributeAWSLambdaFunctionName = "aws.lambda.function.name";
5455
internal static readonly string AttributeAWSLambdaResourceMappingId = "aws.lambda.resource_mapping.id";
5556
internal static readonly string AttributeAWSS3Bucket = "aws.s3.bucket";
@@ -58,6 +59,7 @@ internal sealed class AwsAttributeKeys
5859
internal static readonly string AttributeAWSStepFunctionsActivityArn = "aws.stepfunctions.activity.arn";
5960
internal static readonly string AttributeAWSStepFunctionsStateMachineArn = "aws.stepfunctions.state_machine.arn";
6061

62+
internal static readonly string AttributeAWSBedrockGuardrailArn = "aws.bedrock.guardrail.arn";
6163
internal static readonly string AttributeAWSBedrockGuardrailId = "aws.bedrock.guardrail.id";
6264
internal static readonly string AttributeAWSBedrockAgentId = "aws.bedrock.agent.id";
6365
internal static readonly string AttributeAWSBedrockKnowledgeBaseId = "aws.bedrock.knowledge_base.id";

src/AWS.Distro.OpenTelemetry.AutoInstrumentation/AwsMetricAttributeGenerator.cs

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -414,18 +414,27 @@ private static void SetRemoteResourceTypeAndIdentifier(Activity span, ActivityTa
414414
}
415415
else if (IsKeyPresent(span, AttributeAWSLambdaFunctionName))
416416
{
417+
// Handling downstream Lambda as a service vs. an AWS resource:
418+
// - If the method call is "Invoke", we treat downstream Lambda as a service.
419+
// - Otherwise, we treat it as an AWS resource.
420+
//
421+
// This addresses a Lambda topology issue in Application Signals.
422+
// More context in PR: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
423+
//
424+
// NOTE: The env var LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was introduced as part of this fix.
425+
// It is optional and allow users to override the default value if needed.
417426
if (GetRemoteOperation(span, AttributeRpcMethod) == "Invoke")
418427
{
419-
attributes[AttributeAWSRemoteService] = GetLambdaFunctionNameFromArn(EscapeDelimiters((string?)span.GetTagItem(AttributeAWSLambdaFunctionName)));
428+
attributes[AttributeAWSRemoteService] = EscapeDelimiters((string?)span.GetTagItem(AttributeAWSLambdaFunctionName));
420429

421430
string lambdaRemoteEnv = Environment.GetEnvironmentVariable("LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT") ?? "default";
422431
attributes.Add(AttributeAWSRemoteEnvironment, $"lambda:{lambdaRemoteEnv}");
423432
}
424433
else
425434
{
426435
remoteResourceType = NormalizedLambdaServiceName + "::Function";
427-
remoteResourceIdentifier = GetLambdaFunctionNameFromArn(EscapeDelimiters((string?)span.GetTagItem(AttributeAWSLambdaFunctionName)));
428-
cloudformationPrimaryIdentifier = EscapeDelimiters((string?)span.GetTagItem(AttributeAWSLambdaFunctionName));
436+
remoteResourceIdentifier = EscapeDelimiters((string?)span.GetTagItem(AttributeAWSLambdaFunctionName));
437+
cloudformationPrimaryIdentifier = EscapeDelimiters((string?)span.GetTagItem(AttributeAWSLambdaFunctionArn));
429438
}
430439
}
431440
else if (IsKeyPresent(span, AttributeAWSLambdaResourceMappingId))
@@ -478,6 +487,7 @@ private static void SetRemoteResourceTypeAndIdentifier(Activity span, ActivityTa
478487
{
479488
remoteResourceType = NormalizedBedrockServiceName + "::Guardrail";
480489
remoteResourceIdentifier = EscapeDelimiters((string?)span.GetTagItem(AttributeAWSBedrockGuardrailId));
490+
cloudformationPrimaryIdentifier = EscapeDelimiters((string?)span.GetTagItem(AttributeAWSBedrockGuardrailArn));
481491
}
482492
else if (IsKeyPresent(span, AttributeGenAiModelId))
483493
{
@@ -523,22 +533,6 @@ private static void SetRemoteResourceTypeAndIdentifier(Activity span, ActivityTa
523533
}
524534
}
525535

526-
private static string? GetLambdaFunctionNameFromArn(string? stringArn)
527-
{
528-
if (stringArn == null)
529-
{
530-
return null;
531-
}
532-
533-
if (stringArn.StartsWith("arn:aws:lambda:", StringComparison.Ordinal))
534-
{
535-
string[] parts = stringArn.Split(':');
536-
return parts.Length > 0 ? parts[parts.Length - 1] : null;
537-
}
538-
539-
return stringArn;
540-
}
541-
542536
private static void SetRemoteDbUser(Activity span, ActivityTagsCollection attributes)
543537
{
544538
if (IsDBSpan(span) && IsKeyPresent(span, AttributeDBUser))

src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSSemanticConventions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal static class AWSSemanticConventions
1515
public const string AttributeAWSSQSQueueName = "aws.sqs.queue_name";
1616
public const string AttributeAWSS3BucketName = "aws.s3.bucket";
1717
public const string AttributeAWSKinesisStreamName = "aws.kinesis.stream_name";
18+
public const string AttributeAWSLambdaFunctionArn = "aws.lambda.function.arn";
1819
public const string AttributeAWSLambdaFunctionName = "aws.lambda.function.name";
1920
public const string AttributeAWSLambdaResourceMappingId = "aws.lambda.resource_mapping.id";
2021
public const string AttributeAWSSecretsManagerSecretArn = "aws.secretsmanager.secret.arn";
@@ -23,6 +24,7 @@ internal static class AWSSemanticConventions
2324
public const string AttributeAWSStepFunctionsStateMachineArn = "aws.stepfunctions.state_machine.arn";
2425

2526
// AWS Bedrock service attributes not yet defined in semantic conventions
27+
public const string AttributeAWSBedrockGuardrailArn = "aws.bedrock.guardrail.arn";
2628
public const string AttributeAWSBedrockGuardrailId = "aws.bedrock.guardrail.id";
2729
public const string AttributeAWSBedrockAgentId = "aws.bedrock.agent.id";
2830
public const string AttributeAWSBedrockKnowledgeBaseId = "aws.bedrock.knowledge_base.id";

src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSServiceHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ internal class AWSServiceHelper
2626
{
2727
{ AWSServiceType.SecretsManagerService, new List<string> { "ARN" } },
2828
{ AWSServiceType.SQSService, new List<string> { "QueueUrl" } },
29-
{ AWSServiceType.BedrockService, new List<string> { "GuardrailId" } },
29+
{ AWSServiceType.BedrockService, new List<string> { "GuardrailArn", "GuardrailId" } },
3030
{ AWSServiceType.BedrockAgentService, new List<string> { "AgentId", "DataSourceId" } },
3131
};
3232

@@ -45,6 +45,7 @@ internal class AWSServiceHelper
4545
{ "FunctionName", AWSSemanticConventions.AttributeAWSLambdaFunctionName },
4646
{ "UUID", AWSSemanticConventions.AttributeAWSLambdaResourceMappingId },
4747
{ "ModelId", AWSSemanticConventions.AttributeGenAiModelId },
48+
{ "GuardrailArn", AWSSemanticConventions.AttributeAWSBedrockGuardrailArn },
4849
{ "GuardrailId", AWSSemanticConventions.AttributeAWSBedrockGuardrailId },
4950
{ "AgentId", AWSSemanticConventions.AttributeAWSBedrockAgentId },
5051
{ "KnowledgeBaseId", AWSSemanticConventions.AttributeAWSBedrockKnowledgeBaseId },

src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSTracingPipelineHandler.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@ private static void AddRequestSpecificInformation(Activity activity, IRequestCon
178178
}
179179
}
180180

181+
// for Lambda, FunctionName can be passed as arn, partial arn, or name. Standardize to name.
182+
if (AWSServiceType.IsLambdaService(service) && parameter == "FunctionName")
183+
{
184+
var functionName = property.GetValue(request);
185+
if (functionName != null)
186+
{
187+
var functionNameString = functionName.ToString();
188+
if (functionNameString != null)
189+
{
190+
string[] parts = functionNameString.Split(':');
191+
functionNameString = parts.Length > 0 ? parts[parts.Length - 1] : null;
192+
activity.SetTag(AWSSemanticConventions.AttributeAWSLambdaFunctionName, functionNameString);
193+
continue;
194+
}
195+
}
196+
}
197+
181198
if (AWSServiceHelper.ParameterAttributeMap.TryGetValue(parameter, out var attribute))
182199
{
183200
activity.SetTag(attribute, property.GetValue(request));
@@ -250,6 +267,23 @@ private static void AddResponseSpecificInformation(Activity activity, IResponseC
250267
}
251268
}
252269
}
270+
// for Lambda, extract function ARN from response Configuration object.
271+
if (AWSServiceType.IsLambdaService(service))
272+
{
273+
var configuration = response.GetType().GetProperty("Configuration");
274+
if (configuration != null)
275+
{
276+
var configObject = configuration.GetValue(response);
277+
if (configObject != null)
278+
{
279+
var functionArn = configObject.GetType().GetProperty("FunctionArn");
280+
if (functionArn != null)
281+
{
282+
activity.SetTag(AWSSemanticConventions.AttributeAWSLambdaFunctionArn, functionArn.GetValue(configObject));
283+
}
284+
}
285+
}
286+
}
253287
}
254288

255289
private static void AddBedrockAgentResponseAttribute(Activity activity, AmazonWebServiceResponse response, string parameter)

test/AWS.Distro.OpenTelemetry.AutoInstrumentation.Tests/AwsMetricAttributesGeneratorTest.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,20 @@ public void TestSdkClientSpanWithRemoteResourceAttributes()
904904
};
905905
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::DynamoDB::Table", "aws_table^^name", "aws_table^^name");
906906

907+
attributesCombination = new Dictionary<string, object>
908+
{
909+
{ AttributeAWSLambdaFunctionName, "aws_lambda_function_name" },
910+
{ AttributeAWSLambdaFunctionArn, "arn:aws:lambda:us-west-2:123456789012:function:aws_lambda_function_arn" },
911+
};
912+
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::Lambda::Function", "aws_lambda_function_name", "arn:aws:lambda:us-west-2:123456789012:function:aws_lambda_function_arn");
913+
914+
attributesCombination = new Dictionary<string, object>
915+
{
916+
{ AttributeAWSLambdaFunctionName, "aws_lambda_function_^name" },
917+
{ AttributeAWSLambdaFunctionArn, "arn:aws:lambda:us-west-2:123456789012:function:aws_lambda_function_^arn" },
918+
};
919+
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::Lambda::Function", "aws_lambda_function_^^name", "arn:aws:lambda:us-west-2:123456789012:function:aws_lambda_function_^^arn");
920+
907921
attributesCombination = new Dictionary<string, object>
908922
{
909923
{ AttributeAWSLambdaResourceMappingId, "aws_event_source_mapping_id" },
@@ -967,14 +981,16 @@ public void TestSdkClientSpanWithRemoteResourceAttributes()
967981
attributesCombination = new Dictionary<string, object>
968982
{
969983
{ AttributeAWSBedrockGuardrailId, "aws_guardrail_id" },
984+
{ AttributeAWSBedrockGuardrailArn, "arn:aws:bedrock:us-west-2:123456789012:guardrail/aws_guardrail_arn" },
970985
};
971-
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::Bedrock::Guardrail", "aws_guardrail_id", "aws_guardrail_id");
986+
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::Bedrock::Guardrail", "aws_guardrail_id", "arn:aws:bedrock:us-west-2:123456789012:guardrail/aws_guardrail_arn");
972987

973988
attributesCombination = new Dictionary<string, object>
974989
{
975990
{ AttributeAWSBedrockGuardrailId, "aws_guardrail_^id" },
991+
{ AttributeAWSBedrockGuardrailArn, "arn:aws:bedrock:us-west-2:123456789012:guardrail/aws_guardrail_^arn" },
976992
};
977-
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::Bedrock::Guardrail", "aws_guardrail_^^id", "aws_guardrail_^^id");
993+
this.ValidateRemoteResourceAttributes(attributesCombination, "AWS::Bedrock::Guardrail", "aws_guardrail_^^id", "arn:aws:bedrock:us-west-2:123456789012:guardrail/aws_guardrail_^^arn");
978994

979995
attributesCombination = new Dictionary<string, object>
980996
{

test/contract-tests/images/applications/TestSimpleApp.AWSSDK.Core/BedrockTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public GetGuardrailResponse GetGuardrailResponse()
3636
{
3737
HttpStatusCode = HttpStatusCode.OK,
3838
GuardrailId = "test-guardrail",
39+
GuardrailArn = "arn:aws:bedrock:us-west-2:12345678901:guardrail/test-guardrail",
3940
};
4041
}
4142

test/contract-tests/tests/test/amazon/awssdk/awssdk_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
_AWS_SNS_TOPIC_ARN: str = "aws.sns.topic.arn"
3333
_AWS_STEPFUNCTIONS_ACTIVITY_ARN: str = "aws.stepfunctions.activity.arn"
3434
_AWS_STEPFUNCTIONS_STATE_MACHINE_ARN: str = "aws.stepfunctions.state_machine.arn"
35+
_AWS_BEDROCK_GUARDRAIL_ARN: str = "aws.bedrock.guardrail.arn"
3536
_AWS_BEDROCK_GUARDRAIL_ID: str = "aws.bedrock.guardrail.id"
3637
_AWS_BEDROCK_AGENT_ID: str = "aws.bedrock.agent.id"
3738
_AWS_BEDROCK_KNOWLEDGE_BASE_ID: str = "aws.bedrock.knowledge_base.id"
@@ -543,9 +544,10 @@ def test_bedrock_get_guardrail(self):
543544
remote_operation="GetGuardrail",
544545
remote_resource_type="AWS::Bedrock::Guardrail",
545546
remote_resource_identifier="test-guardrail",
546-
cloudformation_primary_identifier="test-guardrail",
547+
cloudformation_primary_identifier="arn:aws:bedrock:us-west-2:12345678901:guardrail/test-guardrail",
547548
request_response_specific_attributes={
548549
_AWS_BEDROCK_GUARDRAIL_ID: "test-guardrail",
550+
_AWS_BEDROCK_GUARDRAIL_ARN: "arn:aws:bedrock:us-west-2:12345678901:guardrail/test-guardrail",
549551
},
550552
span_name="Bedrock.GetGuardrail",
551553
)

0 commit comments

Comments
 (0)