103
103
_NORMALIZED_LAMBDA_SERVICE_NAME : str = "AWS::Lambda"
104
104
_DB_CONNECTION_STRING_TYPE : str = "DB::Connection"
105
105
106
+ # Constants for Lambda operations
107
+ _LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT : str = "LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT"
108
+ _LAMBDA_INVOKE_OPERATION : str = "Invoke"
109
+
106
110
# Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
107
111
_GRAPHQL : str = "graphql"
108
112
@@ -145,6 +149,7 @@ def _generate_dependency_metric_attributes(span: ReadableSpan, resource: Resourc
145
149
_set_egress_operation (span , attributes )
146
150
_set_remote_service_and_operation (span , attributes )
147
151
_set_remote_type_and_identifier (span , attributes )
152
+ _set_remote_environment (span , attributes )
148
153
_set_remote_db_user (span , attributes )
149
154
_set_span_kind_for_dependency (span , attributes )
150
155
return attributes
@@ -317,7 +322,17 @@ def _normalize_remote_service_name(span: ReadableSpan, service_name: str) -> str
317
322
"Secrets Manager" : _NORMALIZED_SECRETSMANAGER_SERVICE_NAME ,
318
323
"SNS" : _NORMALIZED_SNS_SERVICE_NAME ,
319
324
"SFN" : _NORMALIZED_STEPFUNCTIONS_SERVICE_NAME ,
325
+ "Lambda" : _NORMALIZED_LAMBDA_SERVICE_NAME ,
320
326
}
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
+
321
336
return aws_sdk_service_mapping .get (service_name , "AWS::" + service_name )
322
337
return service_name
323
338
@@ -450,22 +465,9 @@ def _set_remote_type_and_identifier(span: ReadableSpan, attributes: BoundedAttri
450
465
)[- 1 ]
451
466
cloudformation_primary_identifier = _escape_delimiters (span .attributes .get (AWS_STEPFUNCTIONS_ACTIVITY_ARN ))
452
467
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 ):
469
471
remote_resource_type = _NORMALIZED_LAMBDA_SERVICE_NAME + "::Function"
470
472
remote_resource_identifier = _escape_delimiters (span .attributes .get (AWS_LAMBDA_FUNCTION_NAME ))
471
473
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
491
493
attributes [AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER ] = cloudformation_primary_identifier
492
494
493
495
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
+
494
522
def _get_db_connection (span : ReadableSpan ) -> None :
495
523
"""
496
524
RemoteResourceIdentifier is populated with rule:
0 commit comments