Skip to content

Commit 66a653e

Browse files
Merge branch 'main' into dependabot/pip/requests-2.32.4
2 parents 36fab71 + de4b61b commit 66a653e

28 files changed

+2515
-376
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_attribute_keys.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
# AWS_#_NAME attributes are not supported in python as they are not part of the Semantic Conventions.
2020
# TODO:Move to Semantic Conventions when these attributes are added.
21+
AWS_AUTH_ACCESS_KEY: str = "aws.auth.account.access_key"
22+
AWS_AUTH_REGION: str = "aws.auth.region"
2123
AWS_SQS_QUEUE_URL: str = "aws.sqs.queue.url"
2224
AWS_SQS_QUEUE_NAME: str = "aws.sqs.queue.name"
25+
AWS_KINESIS_STREAM_ARN: str = "aws.kinesis.stream.arn"
2326
AWS_KINESIS_STREAM_NAME: str = "aws.kinesis.stream.name"
2427
AWS_BEDROCK_DATA_SOURCE_ID: str = "aws.bedrock.data_source.id"
2528
AWS_BEDROCK_KNOWLEDGE_BASE_ID: str = "aws.bedrock.knowledge_base.id"
@@ -33,4 +36,8 @@
3336
AWS_LAMBDA_FUNCTION_NAME: str = "aws.lambda.function.name"
3437
AWS_LAMBDA_RESOURCEMAPPING_ID: str = "aws.lambda.resource_mapping.id"
3538
AWS_LAMBDA_FUNCTION_ARN: str = "aws.lambda.function.arn"
39+
AWS_DYNAMODB_TABLE_ARN: str = "aws.dynamodb.table.arn"
40+
AWS_REMOTE_RESOURCE_ACCESS_KEY: str = "aws.remote.resource.account.access_key"
41+
AWS_REMOTE_RESOURCE_ACCOUNT_ID: str = "aws.remote.resource.account.id"
42+
AWS_REMOTE_RESOURCE_REGION: str = "aws.remote.resource.region"
3643
AWS_SERVICE_TYPE: str = "aws.service.type"

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_metric_attribute_generator.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
from urllib.parse import ParseResult, urlparse
88

99
from amazon.opentelemetry.distro._aws_attribute_keys import (
10+
AWS_AUTH_ACCESS_KEY,
11+
AWS_AUTH_REGION,
1012
AWS_BEDROCK_AGENT_ID,
1113
AWS_BEDROCK_DATA_SOURCE_ID,
1214
AWS_BEDROCK_GUARDRAIL_ARN,
1315
AWS_BEDROCK_GUARDRAIL_ID,
1416
AWS_BEDROCK_KNOWLEDGE_BASE_ID,
1517
AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER,
18+
AWS_DYNAMODB_TABLE_ARN,
19+
AWS_KINESIS_STREAM_ARN,
1620
AWS_KINESIS_STREAM_NAME,
1721
AWS_LAMBDA_FUNCTION_ARN,
1822
AWS_LAMBDA_FUNCTION_NAME,
@@ -22,7 +26,10 @@
2226
AWS_REMOTE_DB_USER,
2327
AWS_REMOTE_ENVIRONMENT,
2428
AWS_REMOTE_OPERATION,
29+
AWS_REMOTE_RESOURCE_ACCESS_KEY,
30+
AWS_REMOTE_RESOURCE_ACCOUNT_ID,
2531
AWS_REMOTE_RESOURCE_IDENTIFIER,
32+
AWS_REMOTE_RESOURCE_REGION,
2633
AWS_REMOTE_RESOURCE_TYPE,
2734
AWS_REMOTE_SERVICE,
2835
AWS_SECRETSMANAGER_SECRET_ARN,
@@ -56,6 +63,7 @@
5663
SERVICE_METRIC,
5764
MetricAttributeGenerator,
5865
)
66+
from amazon.opentelemetry.distro.regional_resource_arn_parser import RegionalResourceArnParser
5967
from amazon.opentelemetry.distro.sqs_url_parser import SqsUrlParser
6068
from opentelemetry.sdk.resources import Resource
6169
from opentelemetry.sdk.trace import BoundedAttributes, ReadableSpan
@@ -148,7 +156,11 @@ def _generate_dependency_metric_attributes(span: ReadableSpan, resource: Resourc
148156
_set_service(resource, span, attributes)
149157
_set_egress_operation(span, attributes)
150158
_set_remote_service_and_operation(span, attributes)
151-
_set_remote_type_and_identifier(span, attributes)
159+
is_remote_identifier_present = _set_remote_type_and_identifier(span, attributes)
160+
if is_remote_identifier_present:
161+
is_remote_account_id_present = _set_remote_account_id_and_region(span, attributes)
162+
if not is_remote_account_id_present:
163+
_set_remote_access_key_and_region(span, attributes)
152164
_set_remote_environment(span, attributes)
153165
_set_remote_db_user(span, attributes)
154166
_set_span_kind_for_dependency(span, attributes)
@@ -383,7 +395,7 @@ def _generate_remote_operation(span: ReadableSpan) -> str:
383395

384396

385397
# pylint: disable=too-many-branches,too-many-statements
386-
def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttributes) -> None:
398+
def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttributes) -> bool:
387399
"""
388400
Remote resource attributes {@link AwsAttributeKeys#AWS_REMOTE_RESOURCE_TYPE} and {@link
389401
AwsAttributeKeys#AWS_REMOTE_RESOURCE_IDENTIFIER} are used to store information about the resource associated with
@@ -403,9 +415,23 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
403415
if is_key_present(span, _AWS_TABLE_NAMES) and len(span.attributes.get(_AWS_TABLE_NAMES)) == 1:
404416
remote_resource_type = _NORMALIZED_DYNAMO_DB_SERVICE_NAME + "::Table"
405417
remote_resource_identifier = _escape_delimiters(span.attributes.get(_AWS_TABLE_NAMES)[0])
418+
elif is_key_present(span, AWS_DYNAMODB_TABLE_ARN):
419+
remote_resource_type = _NORMALIZED_DYNAMO_DB_SERVICE_NAME + "::Table"
420+
remote_resource_identifier = _escape_delimiters(
421+
RegionalResourceArnParser.extract_dynamodb_table_name_from_arn(
422+
span.attributes.get(AWS_DYNAMODB_TABLE_ARN)
423+
)
424+
)
406425
elif is_key_present(span, AWS_KINESIS_STREAM_NAME):
407426
remote_resource_type = _NORMALIZED_KINESIS_SERVICE_NAME + "::Stream"
408427
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_KINESIS_STREAM_NAME))
428+
elif is_key_present(span, AWS_KINESIS_STREAM_ARN):
429+
remote_resource_type = _NORMALIZED_KINESIS_SERVICE_NAME + "::Stream"
430+
remote_resource_identifier = _escape_delimiters(
431+
RegionalResourceArnParser.extract_kinesis_stream_name_from_arn(
432+
span.attributes.get(AWS_KINESIS_STREAM_ARN)
433+
)
434+
)
409435
elif is_key_present(span, _AWS_BUCKET_NAME):
410436
remote_resource_type = _NORMALIZED_S3_SERVICE_NAME + "::Bucket"
411437
remote_resource_identifier = _escape_delimiters(span.attributes.get(_AWS_BUCKET_NAME))
@@ -442,27 +468,35 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
442468
remote_resource_identifier = _escape_delimiters(span.attributes.get(GEN_AI_REQUEST_MODEL))
443469
elif is_key_present(span, AWS_SECRETSMANAGER_SECRET_ARN):
444470
remote_resource_type = _NORMALIZED_SECRETSMANAGER_SERVICE_NAME + "::Secret"
445-
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SECRETSMANAGER_SECRET_ARN)).split(
446-
":"
447-
)[-1]
471+
remote_resource_identifier = _escape_delimiters(
472+
RegionalResourceArnParser.extract_resource_name_from_arn(
473+
span.attributes.get(AWS_SECRETSMANAGER_SECRET_ARN)
474+
)
475+
)
448476
cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_SECRETSMANAGER_SECRET_ARN))
449477
elif is_key_present(span, AWS_SNS_TOPIC_ARN):
450478
remote_resource_type = _NORMALIZED_SNS_SERVICE_NAME + "::Topic"
451-
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_SNS_TOPIC_ARN)).split(":")[-1]
479+
remote_resource_identifier = _escape_delimiters(
480+
RegionalResourceArnParser.extract_resource_name_from_arn(span.attributes.get(AWS_SNS_TOPIC_ARN))
481+
)
452482
cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_SNS_TOPIC_ARN))
453483
elif is_key_present(span, AWS_STEPFUNCTIONS_STATEMACHINE_ARN):
454484
remote_resource_type = _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + "::StateMachine"
455485
remote_resource_identifier = _escape_delimiters(
456-
span.attributes.get(AWS_STEPFUNCTIONS_STATEMACHINE_ARN)
457-
).split(":")[-1]
486+
RegionalResourceArnParser.extract_resource_name_from_arn(
487+
span.attributes.get(AWS_STEPFUNCTIONS_STATEMACHINE_ARN)
488+
)
489+
)
458490
cloudformation_primary_identifier = _escape_delimiters(
459491
span.attributes.get(AWS_STEPFUNCTIONS_STATEMACHINE_ARN)
460492
)
461493
elif is_key_present(span, AWS_STEPFUNCTIONS_ACTIVITY_ARN):
462494
remote_resource_type = _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + "::Activity"
463-
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_STEPFUNCTIONS_ACTIVITY_ARN)).split(
464-
":"
465-
)[-1]
495+
remote_resource_identifier = _escape_delimiters(
496+
RegionalResourceArnParser.extract_resource_name_from_arn(
497+
span.attributes.get(AWS_STEPFUNCTIONS_ACTIVITY_ARN)
498+
)
499+
)
466500
cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_STEPFUNCTIONS_ACTIVITY_ARN))
467501
elif is_key_present(span, AWS_LAMBDA_FUNCTION_NAME):
468502
# For non-Invoke Lambda operations, treat Lambda as a resource,
@@ -491,6 +525,48 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
491525
attributes[AWS_REMOTE_RESOURCE_TYPE] = remote_resource_type
492526
attributes[AWS_REMOTE_RESOURCE_IDENTIFIER] = remote_resource_identifier
493527
attributes[AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudformation_primary_identifier
528+
return True
529+
return False
530+
531+
532+
def _set_remote_account_id_and_region(span: ReadableSpan, attributes: BoundedAttributes) -> bool:
533+
arn_attributes = [
534+
AWS_DYNAMODB_TABLE_ARN,
535+
AWS_KINESIS_STREAM_ARN,
536+
AWS_SNS_TOPIC_ARN,
537+
AWS_SECRETSMANAGER_SECRET_ARN,
538+
AWS_STEPFUNCTIONS_STATEMACHINE_ARN,
539+
AWS_STEPFUNCTIONS_ACTIVITY_ARN,
540+
AWS_BEDROCK_GUARDRAIL_ARN,
541+
AWS_LAMBDA_FUNCTION_ARN,
542+
]
543+
remote_account_id: Optional[str] = None
544+
remote_region: Optional[str] = None
545+
546+
if is_key_present(span, AWS_SQS_QUEUE_URL):
547+
queue_url = _escape_delimiters(span.attributes.get(AWS_SQS_QUEUE_URL))
548+
remote_account_id = SqsUrlParser.get_account_id(queue_url)
549+
remote_region = SqsUrlParser.get_region(queue_url)
550+
else:
551+
for arn_attribute in arn_attributes:
552+
if is_key_present(span, arn_attribute):
553+
arn = span.attributes.get(arn_attribute)
554+
remote_account_id = RegionalResourceArnParser.get_account_id(arn)
555+
remote_region = RegionalResourceArnParser.get_region(arn)
556+
break
557+
558+
if remote_account_id is not None and remote_region is not None:
559+
attributes[AWS_REMOTE_RESOURCE_ACCOUNT_ID] = remote_account_id
560+
attributes[AWS_REMOTE_RESOURCE_REGION] = remote_region
561+
return True
562+
return False
563+
564+
565+
def _set_remote_access_key_and_region(span: ReadableSpan, attributes: BoundedAttributes) -> None:
566+
if is_key_present(span, AWS_AUTH_ACCESS_KEY):
567+
attributes[AWS_REMOTE_RESOURCE_ACCESS_KEY] = span.attributes.get(AWS_AUTH_ACCESS_KEY)
568+
if is_key_present(span, AWS_AUTH_REGION):
569+
attributes[AWS_REMOTE_RESOURCE_REGION] = span.attributes.get(AWS_AUTH_REGION)
494570

495571

496572
def _set_remote_environment(span: ReadableSpan, attributes: BoundedAttributes) -> None:

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_utils.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
from importlib.metadata import PackageNotFoundError, version
66
from logging import Logger, getLogger
7+
from typing import Optional
78

89
from packaging.requirements import Requirement
910

@@ -37,30 +38,44 @@ def is_agent_observability_enabled() -> bool:
3738
return os.environ.get(AGENT_OBSERVABILITY_ENABLED, "false").lower() == "true"
3839

3940

40-
def get_aws_region() -> str:
41-
"""Get AWS region using botocore session.
41+
IS_BOTOCORE_INSTALLED: bool = is_installed("botocore")
4242

43-
botocore automatically checks in the following priority order:
44-
1. AWS_REGION environment variable
45-
2. AWS_DEFAULT_REGION environment variable
46-
3. AWS CLI config file (~/.aws/config)
47-
4. EC2 instance metadata service
4843

49-
Returns:
50-
The AWS region if found, None otherwise.
44+
def get_aws_session():
45+
"""
46+
Returns a botocore session only if botocore is installed, otherwise None.
47+
If AWS Region is defined in `AWS_REGION` or `AWS_DEFAULT_REGION` environment variables,
48+
then the region is set in the botocore session before returning.
49+
50+
We do this to prevent runtime errors for ADOT customers that do not need
51+
any features that require botocore.
5152
"""
52-
if is_installed("botocore"):
53-
try:
54-
from botocore import session # pylint: disable=import-outside-toplevel
55-
56-
botocore_session = session.Session()
57-
if botocore_session.region_name:
58-
return botocore_session.region_name
59-
except (ImportError, AttributeError):
60-
# botocore failed to determine region
61-
pass
62-
63-
_logger.warning(
64-
"AWS region not found. Please set AWS_REGION environment variable or configure AWS CLI with 'aws configure'."
65-
)
53+
if IS_BOTOCORE_INSTALLED:
54+
# pylint: disable=import-outside-toplevel
55+
from botocore.session import Session
56+
57+
session = Session()
58+
# Botocore only looks up AWS_DEFAULT_REGION when creating a session/client
59+
# See: https://docs.aws.amazon.com/sdkref/latest/guide/feature-region.html#feature-region-sdk-compat
60+
region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
61+
if region:
62+
session.set_config_variable("region", region)
63+
return session
6664
return None
65+
66+
67+
def get_aws_region() -> Optional[str]:
68+
"""Get AWS region from environment or botocore session.
69+
70+
Returns the AWS region in the following priority order:
71+
1. AWS_REGION environment variable
72+
2. AWS_DEFAULT_REGION environment variable
73+
3. botocore session's region (if botocore is available)
74+
4. None if no region can be determined
75+
"""
76+
botocore_session = get_aws_session()
77+
return botocore_session.get_config_variable("region") if botocore_session else None
78+
79+
80+
def is_account_id(input_str: str) -> bool:
81+
return input_str is not None and input_str.isdigit()

0 commit comments

Comments
 (0)