Skip to content

Commit 3c105e4

Browse files
authored
Merge branch 'main' into genesis_baggage_support
2 parents f7eaa2c + 36a07d8 commit 3c105e4

File tree

3 files changed

+380
-38
lines changed

3 files changed

+380
-38
lines changed

.github/workflows/release-lambda.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ permissions:
2121

2222
jobs:
2323
build-layer:
24+
environment: Release
2425
runs-on: ubuntu-latest
2526
outputs:
2627
aws_regions_json: ${{ steps.set-matrix.outputs.aws_regions_json }}

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

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@
103103
_NORMALIZED_LAMBDA_SERVICE_NAME: str = "AWS::Lambda"
104104
_DB_CONNECTION_STRING_TYPE: str = "DB::Connection"
105105

106+
# Constants for Lambda operations
107+
_LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT: str = "LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT"
108+
_LAMBDA_INVOKE_OPERATION: str = "Invoke"
109+
106110
# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
107111
_GRAPHQL: str = "graphql"
108112

@@ -145,6 +149,7 @@ def _generate_dependency_metric_attributes(span: ReadableSpan, resource: Resourc
145149
_set_egress_operation(span, attributes)
146150
_set_remote_service_and_operation(span, attributes)
147151
_set_remote_type_and_identifier(span, attributes)
152+
_set_remote_environment(span, attributes)
148153
_set_remote_db_user(span, attributes)
149154
_set_span_kind_for_dependency(span, attributes)
150155
return attributes
@@ -317,7 +322,17 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
317322
"Secrets Manager": _NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
318323
"SNS": _NORMALIZED_SNS_SERVICE_NAME,
319324
"SFN": _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME,
325+
"Lambda": _NORMALIZED_LAMBDA_SERVICE_NAME,
320326
}
327+
328+
# Special handling for Lambda invoke operations
329+
if _is_lambda_invoke_operation(span):
330+
lambda_function_name = span.attributes.get(AWS_LAMBDA_FUNCTION_NAME)
331+
# If Lambda name is not present, use UnknownRemoteService
332+
# This is intentional - we want to clearly indicate when the Lambda function name
333+
# is missing rather than falling back to a generic service name
334+
return lambda_function_name if lambda_function_name else UNKNOWN_REMOTE_SERVICE
335+
321336
return aws_sdk_service_mapping.get(service_name, "AWS::" + service_name)
322337
return service_name
323338

@@ -450,22 +465,9 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
450465
)[-1]
451466
cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_STEPFUNCTIONS_ACTIVITY_ARN))
452467
elif is_key_present(span, AWS_LAMBDA_FUNCTION_NAME):
453-
# Handling downstream Lambda as a service vs. an AWS resource:
454-
# - If the method call is "Invoke", we treat downstream Lambda as a service.
455-
# - Otherwise, we treat it as an AWS resource.
456-
#
457-
# This addresses a Lambda topology issue in Application Signals.
458-
# More context in PR: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
459-
#
460-
# NOTE: The env var LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was introduced as part of this fix.
461-
# It is optional and allows users to override the default value if needed.
462-
if span.attributes.get(_RPC_METHOD) == "Invoke":
463-
attributes[AWS_REMOTE_SERVICE] = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_NAME))
464-
465-
attributes[AWS_REMOTE_ENVIRONMENT] = (
466-
f'lambda:{os.environ.get("LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT", "default")}'
467-
)
468-
else:
468+
# For non-Invoke Lambda operations, treat Lambda as a resource,
469+
# see normalize_remote_service_name for more information.
470+
if not _is_lambda_invoke_operation(span):
469471
remote_resource_type = _NORMALIZED_LAMBDA_SERVICE_NAME + "::Function"
470472
remote_resource_identifier = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_NAME))
471473
cloudformation_primary_identifier = _escape_delimiters(span.attributes.get(AWS_LAMBDA_FUNCTION_ARN))
@@ -491,6 +493,32 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
491493
attributes[AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudformation_primary_identifier
492494

493495

496+
def _set_remote_environment(span: ReadableSpan, attributes: BoundedAttributes) -> None:
497+
"""
498+
Remote environment is used to identify the environment of downstream services. Currently only
499+
set to "lambda:default" for Lambda Invoke operations when aws-api system is detected.
500+
"""
501+
# We want to treat downstream Lambdas as a service rather than a resource because
502+
# Application Signals topology map gets disconnected due to conflicting Lambda Entity
503+
# definitions
504+
# Additional context can be found in
505+
# https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
506+
if _is_lambda_invoke_operation(span):
507+
remote_environment = os.environ.get(_LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT, "").strip()
508+
if not remote_environment:
509+
remote_environment = "default"
510+
attributes[AWS_REMOTE_ENVIRONMENT] = f"lambda:{remote_environment}"
511+
512+
513+
def _is_lambda_invoke_operation(span: ReadableSpan) -> bool:
514+
"""Check if the span represents a Lambda Invoke operation."""
515+
if not is_aws_sdk_span(span):
516+
return False
517+
518+
rpc_service = _get_remote_service(span, _RPC_SERVICE)
519+
return rpc_service == "Lambda" and span.attributes.get(_RPC_METHOD) == _LAMBDA_INVOKE_OPERATION
520+
521+
494522
def _get_db_connection(span: ReadableSpan) -> None:
495523
"""
496524
RemoteResourceIdentifier is populated with rule:

0 commit comments

Comments
 (0)