@@ -33,6 +33,7 @@ import {
33
33
SERVICE_METRIC ,
34
34
} from './metric-attribute-generator' ;
35
35
import { SqsUrlParser } from './sqs-url-parser' ;
36
+ import { LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT } from './aws-opentelemetry-configurator' ;
36
37
37
38
// Does not exist in @opentelemetry /semantic-conventions
38
39
const _SERVER_SOCKET_ADDRESS : string = 'server.socket.address' ;
@@ -50,6 +51,9 @@ const _GRAPHQL_OPERATION_TYPE: string = 'graphql.operation.type';
50
51
// Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present.
51
52
const GRAPHQL : string = 'graphql' ;
52
53
54
+ // Constants for Lambda operations
55
+ const LAMBDA_INVOKE_OPERATION : string = 'Invoke' ;
56
+
53
57
// Normalized remote service names for supported AWS services
54
58
const NORMALIZED_DYNAMO_DB_SERVICE_NAME : string = 'AWS::DynamoDB' ;
55
59
const NORMALIZED_KINESIS_SERVICE_NAME : string = 'AWS::Kinesis' ;
@@ -109,6 +113,7 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
109
113
AwsMetricAttributeGenerator . setEgressOperation ( span , attributes ) ;
110
114
AwsMetricAttributeGenerator . setRemoteServiceAndOperation ( span , attributes ) ;
111
115
AwsMetricAttributeGenerator . setRemoteResourceTypeAndIdentifier ( span , attributes ) ;
116
+ AwsMetricAttributeGenerator . setRemoteEnvironment ( span , attributes ) ;
112
117
AwsMetricAttributeGenerator . setSpanKindForDependency ( span , attributes ) ;
113
118
AwsMetricAttributeGenerator . setRemoteDbUser ( span , attributes ) ;
114
119
@@ -336,7 +341,18 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
336
341
BedrockRuntime : NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME ,
337
342
SecretsManager : NORMALIZED_SECRETSMANAGER_SERVICE_NAME ,
338
343
SFN : NORMALIZED_STEPFUNCTIONS_SERVICE_NAME ,
344
+ Lambda : NORMALIZED_LAMBDA_SERVICE_NAME ,
339
345
} ;
346
+
347
+ // Special handling for Lambda invoke operations
348
+ if ( AwsMetricAttributeGenerator . isLambdaInvokeOperation ( span ) ) {
349
+ const lambdaFunctionName = span . attributes [ AWS_ATTRIBUTE_KEYS . AWS_LAMBDA_FUNCTION_NAME ] ;
350
+ // If Lambda name is not present, use UnknownRemoteService
351
+ // This is intentional - we want to clearly indicate when the Lambda function name
352
+ // is missing rather than falling back to a generic service name
353
+ return lambdaFunctionName ? String ( lambdaFunctionName ) : AwsSpanProcessingUtil . UNKNOWN_REMOTE_SERVICE ;
354
+ }
355
+
340
356
return awsSdkServiceMapping [ serviceName ] || 'AWS::' + serviceName ;
341
357
}
342
358
return serviceName ;
@@ -410,24 +426,9 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
410
426
) ;
411
427
cloudFormationIdentifier = AwsMetricAttributeGenerator . escapeDelimiters ( activityArn ) ;
412
428
} else if ( AwsSpanProcessingUtil . isKeyPresent ( span , AWS_ATTRIBUTE_KEYS . AWS_LAMBDA_FUNCTION_NAME ) ) {
413
- // Handling downstream Lambda as a service vs. an AWS resource:
414
- // - If the method call is "Invoke", we treat downstream Lambda as a service.
415
- // - Otherwise, we treat it as an AWS resource.
416
- //
417
- // This addresses a Lambda topology issue in Application Signals.
418
- // More context in PR: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
419
- //
420
- // NOTE: The env var LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was introduced as part of this fix.
421
- // It is optional and allow users to override the default value if needed.
422
- if ( AwsMetricAttributeGenerator . getRemoteOperation ( span , SEMATTRS_RPC_METHOD ) === 'Invoke' ) {
423
- attributes [ AWS_ATTRIBUTE_KEYS . AWS_REMOTE_SERVICE ] = AwsMetricAttributeGenerator . escapeDelimiters (
424
- span . attributes [ AWS_ATTRIBUTE_KEYS . AWS_LAMBDA_FUNCTION_NAME ]
425
- ) ;
426
-
427
- attributes [ AWS_ATTRIBUTE_KEYS . AWS_REMOTE_ENVIRONMENT ] = `lambda:${
428
- process . env . LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT || 'default'
429
- } `;
430
- } else {
429
+ // For non-Invoke Lambda operations, treat Lambda as a resource,
430
+ // see normalizeRemoteServiceName for more information.
431
+ if ( ! AwsMetricAttributeGenerator . isLambdaInvokeOperation ( span ) ) {
431
432
remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::Function' ;
432
433
remoteResourceIdentifier = AwsMetricAttributeGenerator . escapeDelimiters (
433
434
span . attributes [ AWS_ATTRIBUTE_KEYS . AWS_LAMBDA_FUNCTION_NAME ]
@@ -491,17 +492,33 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
491
492
remoteResourceIdentifier = AwsMetricAttributeGenerator . getDbConnection ( span ) ;
492
493
}
493
494
495
+ if ( cloudFormationIdentifier === undefined ) {
496
+ cloudFormationIdentifier = remoteResourceIdentifier ;
497
+ }
498
+
494
499
if ( remoteResourceType !== undefined && remoteResourceIdentifier !== undefined ) {
495
500
attributes [ AWS_ATTRIBUTE_KEYS . AWS_REMOTE_RESOURCE_TYPE ] = remoteResourceType ;
496
501
attributes [ AWS_ATTRIBUTE_KEYS . AWS_REMOTE_RESOURCE_IDENTIFIER ] = remoteResourceIdentifier ;
502
+ attributes [ AWS_ATTRIBUTE_KEYS . AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER ] = cloudFormationIdentifier ;
503
+ }
504
+ }
497
505
498
- if ( AwsSpanProcessingUtil . isAwsSDKSpan ( span ) ) {
499
- if ( cloudFormationIdentifier === undefined ) {
500
- cloudFormationIdentifier = remoteResourceIdentifier ;
501
- }
502
-
503
- attributes [ AWS_ATTRIBUTE_KEYS . AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER ] = cloudFormationIdentifier ;
506
+ /**
507
+ * Remote environment is used to identify the environment of downstream services. Currently only
508
+ * set to "lambda:default" for Lambda Invoke operations when aws-api system is detected.
509
+ */
510
+ private static setRemoteEnvironment ( span : ReadableSpan , attributes : Attributes ) : void {
511
+ // We want to treat downstream Lambdas as a service rather than a resource because
512
+ // Application Signals topology map gets disconnected due to conflicting Lambda Entity
513
+ // definitions
514
+ // Additional context can be found in
515
+ // https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319
516
+ if ( AwsMetricAttributeGenerator . isLambdaInvokeOperation ( span ) ) {
517
+ let remoteEnvironment = process . env [ LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT ] ?. trim ( ) ;
518
+ if ( ! remoteEnvironment ) {
519
+ remoteEnvironment = 'default' ;
504
520
}
521
+ attributes [ AWS_ATTRIBUTE_KEYS . AWS_REMOTE_ENVIRONMENT ] = `lambda:${ remoteEnvironment } ` ;
505
522
}
506
523
}
507
524
@@ -620,6 +637,18 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
620
637
return input . split ( '^' ) . join ( '^^' ) . split ( '|' ) . join ( '^|' ) ;
621
638
}
622
639
640
+ /**
641
+ * Check if the span represents a Lambda Invoke operation.
642
+ */
643
+ private static isLambdaInvokeOperation ( span : ReadableSpan ) : boolean {
644
+ if ( ! AwsSpanProcessingUtil . isAwsSDKSpan ( span ) ) {
645
+ return false ;
646
+ }
647
+
648
+ const rpcService = AwsMetricAttributeGenerator . getRemoteService ( span , SEMATTRS_RPC_SERVICE ) ;
649
+ return rpcService === 'Lambda' && span . attributes [ SEMATTRS_RPC_METHOD ] === LAMBDA_INVOKE_OPERATION ;
650
+ }
651
+
623
652
// Extracts the name of the resource from an arn
624
653
private static extractResourceNameFromArn ( attribute : AttributeValue | undefined ) : string | undefined {
625
654
if ( typeof attribute === 'string' && attribute . startsWith ( 'arn:aws:' ) ) {
0 commit comments