UNPKG

@aws/aws-distro-opentelemetry-node-autoinstrumentation

Version:

This package provides Amazon Web Services distribution of the OpenTelemetry Node Instrumentation, which allows for auto-instrumentation of NodeJS applications.

597 lines 39.3 kB
"use strict"; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); exports.AwsMetricAttributeGenerator = void 0; const api_1 = require("@opentelemetry/api"); const resources_1 = require("@opentelemetry/resources"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const aws_attribute_keys_1 = require("./aws-attribute-keys"); const aws_span_processing_util_1 = require("./aws-span-processing-util"); const metric_attribute_generator_1 = require("./metric-attribute-generator"); const sqs_url_parser_1 = require("./sqs-url-parser"); // Does not exist in @opentelemetry/semantic-conventions const _SERVER_SOCKET_ADDRESS = 'server.socket.address'; const _SERVER_SOCKET_PORT = 'server.socket.port'; const _NET_SOCK_PEER_ADDR = 'net.sock.peer.addr'; const _NET_SOCK_PEER_PORT = 'net.sock.peer.port'; // Alternatively, `import { SemanticAttributes } from '@opentelemetry/instrumentation-undici/build/src/enums/SemanticAttributes';` // SemanticAttributes.SERVER_ADDRESS // SemanticAttributes.SERVER_PORT const _SERVER_ADDRESS = 'server.address'; const _SERVER_PORT = 'server.port'; // Alternatively, `import { AttributeNames } from '@opentelemetry/instrumentation-graphql/build/src/enums/AttributeNames';` // AttributeNames.OPERATION_TYPE const _GRAPHQL_OPERATION_TYPE = 'graphql.operation.type'; // Special DEPENDENCY attribute value if GRAPHQL_OPERATION_TYPE attribute key is present. const GRAPHQL = 'graphql'; // Normalized remote service names for supported AWS services const NORMALIZED_DYNAMO_DB_SERVICE_NAME = 'AWS::DynamoDB'; const NORMALIZED_KINESIS_SERVICE_NAME = 'AWS::Kinesis'; const NORMALIZED_S3_SERVICE_NAME = 'AWS::S3'; const NORMALIZED_SQS_SERVICE_NAME = 'AWS::SQS'; const NORMALIZED_SNS_SERVICE_NAME = 'AWS::SNS'; const NORMALIZED_SECRETSMANAGER_SERVICE_NAME = 'AWS::SecretsManager'; const NORMALIZED_STEPFUNCTIONS_SERVICE_NAME = 'AWS::StepFunctions'; const NORMALIZED_LAMBDA_SERVICE_NAME = 'AWS::Lambda'; const NORMALIZED_BEDROCK_SERVICE_NAME = 'AWS::Bedrock'; const NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME = 'AWS::BedrockRuntime'; const DB_CONNECTION_RESOURCE_TYPE = 'DB::Connection'; // As per https://opentelemetry.io/docs/specs/semconv/resource/#service, if service name is not specified, SDK defaults // the service name to unknown_service:<process name> or just unknown_service. // - https://github.com/open-telemetry/opentelemetry-js/blob/b2778e1b2ff7b038cebf371f1eb9f808fd98107f/packages/opentelemetry-resources/src/platform/node/default-service-name.ts#L16. // - `defaultServiceName()` returns `unknown_service:${process.argv0}` const OTEL_UNKNOWN_SERVICE = (0, resources_1.defaultServiceName)(); /** * AwsMetricAttributeGenerator generates very specific metric attributes based on low-cardinality * span and resource attributes. If such attributes are not present, we fallback to default values. * * <p>The goal of these particular metric attributes is to get metrics for incoming and outgoing * traffic for a service. Namely, {@link SpanKind.SERVER} and {@link SpanKind.CONSUMER} spans * represent "incoming" traffic, {@link SpanKind.CLIENT} and {@link SpanKind.PRODUCER} spans * represent "outgoing" traffic, and {@link SpanKind.INTERNAL} spans are ignored. */ class AwsMetricAttributeGenerator { // This method is used by the AwsSpanMetricsProcessor to generate service and dependency metrics generateMetricAttributeMapFromSpan(span, resource) { const attributesMap = {}; if (aws_span_processing_util_1.AwsSpanProcessingUtil.shouldGenerateServiceMetricAttributes(span)) { attributesMap[metric_attribute_generator_1.SERVICE_METRIC] = this.generateServiceMetricAttributes(span, resource); } if (aws_span_processing_util_1.AwsSpanProcessingUtil.shouldGenerateDependencyMetricAttributes(span)) { attributesMap[metric_attribute_generator_1.DEPENDENCY_METRIC] = this.generateDependencyMetricAttributes(span, resource); } return attributesMap; } generateServiceMetricAttributes(span, resource) { const attributes = {}; AwsMetricAttributeGenerator.setService(resource, span, attributes); AwsMetricAttributeGenerator.setIngressOperation(span, attributes); AwsMetricAttributeGenerator.setSpanKindForService(span, attributes); return attributes; } generateDependencyMetricAttributes(span, resource) { const attributes = {}; AwsMetricAttributeGenerator.setService(resource, span, attributes); AwsMetricAttributeGenerator.setEgressOperation(span, attributes); AwsMetricAttributeGenerator.setRemoteServiceAndOperation(span, attributes); AwsMetricAttributeGenerator.setRemoteResourceTypeAndIdentifier(span, attributes); AwsMetricAttributeGenerator.setSpanKindForDependency(span, attributes); AwsMetricAttributeGenerator.setRemoteDbUser(span, attributes); return attributes; } /** Service is always derived from {@link SEMRESATTRS_SERVICE_NAME} */ static setService(resource, span, attributes) { let service = resource.attributes[semantic_conventions_1.SEMRESATTRS_SERVICE_NAME]; // In practice the service name is never undefined, but we can be defensive here. if (service === undefined || service === OTEL_UNKNOWN_SERVICE) { AwsMetricAttributeGenerator.logUnknownAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_SERVICE, span); service = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_SERVICE; } attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_SERVICE] = service; } /** * Ingress operation (i.e. operation for Server and Consumer spans) will be generated from * "http.method + http.target/with the first API path parameter" if the default span name equals * null, UnknownOperation or http.method value. */ static setIngressOperation(span, attributes) { const operation = aws_span_processing_util_1.AwsSpanProcessingUtil.getIngressOperation(span); if (operation === aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_OPERATION) { AwsMetricAttributeGenerator.logUnknownAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION, span); } attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION] = operation; } /** * Egress operation (i.e. operation for Client and Producer spans) is always derived from a * special span attribute, {@link AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION}. This attribute is * generated with a separate SpanProcessor, {@link AttributePropagatingSpanProcessor} */ static setEgressOperation(span, attributes) { let operation = aws_span_processing_util_1.AwsSpanProcessingUtil.getEgressOperation(span); if (operation === undefined) { AwsMetricAttributeGenerator.logUnknownAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION, span); operation = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_OPERATION; } attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION] = operation; } /** * Remote attributes (only for Client and Producer spans) are generated based on low-cardinality * span attributes, in priority order. * * <p>The first priority is the AWS Remote attributes, which are generated from manually * instrumented span attributes, and are clear indications of customer intent. If AWS Remote * attributes are not present, the next highest priority span attribute is Peer Service, which is * also a reliable indicator of customer intent. If this is set, it will override * AWS_REMOTE_SERVICE identified from any other span attribute, other than AWS Remote attributes. * * <p>After this, we look for the following low-cardinality span attributes that can be used to * determine the remote metric attributes: * * <ul> * <li>RPC * <li>DB * <li>FAAS * <li>Messaging * <li>GraphQL - Special case, if {@link _GRAPHQL_OPERATION_TYPE} is present, * we use it for RemoteOperation and set RemoteService to {@link GRAPHQL}. * </ul> * * <p>In each case, these span attributes were selected from the OpenTelemetry trace semantic * convention specifications as they adhere to the three following criteria: * * <ul> * <li>Attributes are meaningfully indicative of remote service/operation names. * <li>Attributes are defined in the specification to be low cardinality, usually with a low- * cardinality list of values. * <li>Attributes are confirmed to have low-cardinality values, based on code analysis. * </ul> * * if the selected attributes are still producing the UnknownRemoteService or * UnknownRemoteOperation, `net.peer.name`, `net.peer.port`, `net.peer.sock.addr`, * `net.peer.sock.port` and `http.url` will be used to derive the RemoteService. And `http.method` * and `http.url` will be used to derive the RemoteOperation. */ static setRemoteServiceAndOperation(span, attributes) { let remoteService = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE; let remoteOperation = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE) || aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_OPERATION)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE); remoteOperation = AwsMetricAttributeGenerator.getRemoteOperation(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_OPERATION); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_RPC_SERVICE) || aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_RPC_METHOD)) { remoteService = AwsMetricAttributeGenerator.normalizeRemoteServiceName(span, AwsMetricAttributeGenerator.getRemoteService(span, semantic_conventions_1.SEMATTRS_RPC_SERVICE)); remoteOperation = AwsMetricAttributeGenerator.getRemoteOperation(span, semantic_conventions_1.SEMATTRS_RPC_METHOD); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isDBSpan(span)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, semantic_conventions_1.SEMATTRS_DB_SYSTEM); if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_DB_OPERATION)) { remoteOperation = AwsMetricAttributeGenerator.getRemoteOperation(span, semantic_conventions_1.SEMATTRS_DB_OPERATION); } else { remoteOperation = AwsMetricAttributeGenerator.getDBStatementRemoteOperation(span, semantic_conventions_1.SEMATTRS_DB_STATEMENT); } } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_FAAS_INVOKED_NAME) || aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_FAAS_TRIGGER)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, semantic_conventions_1.SEMATTRS_FAAS_INVOKED_NAME); remoteOperation = AwsMetricAttributeGenerator.getRemoteOperation(span, semantic_conventions_1.SEMATTRS_FAAS_TRIGGER); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_MESSAGING_SYSTEM) || aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_MESSAGING_OPERATION)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, semantic_conventions_1.SEMATTRS_MESSAGING_SYSTEM); remoteOperation = AwsMetricAttributeGenerator.getRemoteOperation(span, semantic_conventions_1.SEMATTRS_MESSAGING_OPERATION); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, _GRAPHQL_OPERATION_TYPE)) { remoteService = GRAPHQL; remoteOperation = AwsMetricAttributeGenerator.getRemoteOperation(span, _GRAPHQL_OPERATION_TYPE); } // Peer service takes priority as RemoteService over everything but AWS Remote. if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_PEER_SERVICE) && !aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, semantic_conventions_1.SEMATTRS_PEER_SERVICE); } // try to derive RemoteService and RemoteOperation from the other related attributes if (remoteService === aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE) { remoteService = AwsMetricAttributeGenerator.generateRemoteService(span); } if (remoteOperation === aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION) { remoteOperation = AwsMetricAttributeGenerator.generateRemoteOperation(span); } attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE] = remoteService; attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_OPERATION] = remoteOperation; } /** * When the remote call operation is undetermined for http use cases, will try to extract the * remote operation name from http url string */ static generateRemoteOperation(span) { let remoteOperation = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_HTTP_URL)) { const httpUrl = span.attributes[semantic_conventions_1.SEMATTRS_HTTP_URL]; try { let url; if (typeof httpUrl === 'string') { url = new URL(httpUrl); remoteOperation = aws_span_processing_util_1.AwsSpanProcessingUtil.extractAPIPathValue(url.pathname); } } catch (e) { api_1.diag.verbose(`invalid http.url attribute: ${httpUrl}`); } } const httpMethod = span.attributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD]; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_HTTP_METHOD) && typeof httpMethod === 'string') { remoteOperation = httpMethod + ' ' + remoteOperation; } if (remoteOperation === aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION) { AwsMetricAttributeGenerator.logUnknownAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_OPERATION, span); } return remoteOperation; } static generateRemoteService(span) { let remoteService = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_NET_PEER_NAME)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, semantic_conventions_1.SEMATTRS_NET_PEER_NAME); if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_NET_PEER_PORT)) { const port = span.attributes[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]; remoteService += ':' + port; } } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, _NET_SOCK_PEER_ADDR)) { remoteService = AwsMetricAttributeGenerator.getRemoteService(span, _NET_SOCK_PEER_ADDR); if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, _NET_SOCK_PEER_PORT)) { const port = span.attributes[_NET_SOCK_PEER_PORT]; remoteService += ':' + port; } } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_HTTP_URL)) { const httpUrl = span.attributes[semantic_conventions_1.SEMATTRS_HTTP_URL]; try { const url = new URL(httpUrl); if (url.hostname !== '') { remoteService = url.hostname; if (url.port !== '') { remoteService += ':' + url.port; } } } catch (e) { api_1.diag.verbose(`invalid http.url attribute: ${httpUrl}`); } } else { AwsMetricAttributeGenerator.logUnknownAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE, span); } return remoteService; } /** * If the span is an AWS SDK span, normalize the name to align with <a * href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS * Cloud Control resource format</a> as much as possible, with special attention to services we * can detect remote resource information for. Long term, we would like to normalize service name * in the upstream. * * For Bedrock, Bedrock Agent, and Bedrock Agent Runtime, we can align with AWS Cloud Control and use * AWS::Bedrock for RemoteService. For BedrockRuntime, we are using AWS::BedrockRuntime * as the associated remote resource (Model) is not listed in Cloud Control. */ static normalizeRemoteServiceName(span, serviceName) { if (aws_span_processing_util_1.AwsSpanProcessingUtil.isAwsSDKSpan(span)) { const awsSdkServiceMapping = { BedrockAgent: NORMALIZED_BEDROCK_SERVICE_NAME, BedrockAgentRuntime: NORMALIZED_BEDROCK_SERVICE_NAME, BedrockRuntime: NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME, SecretsManager: NORMALIZED_SECRETSMANAGER_SERVICE_NAME, SFN: NORMALIZED_STEPFUNCTIONS_SERVICE_NAME, }; return awsSdkServiceMapping[serviceName] || 'AWS::' + serviceName; } return serviceName; } /** * Remote resource attributes {@link AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE} and * {@link AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER} are used to store information about the * resource associated with the remote invocation, such as S3 bucket name, etc. We should only * ever set both type and identifier or neither. If any identifier value contains | or ^ , they * will be replaced with ^| or ^^. * * <p>AWS resources type and identifier adhere to <a * href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">AWS * Cloud Control resource format</a>. */ static setRemoteResourceTypeAndIdentifier(span, attributes) { let remoteResourceType; let remoteResourceIdentifier; let cloudFormationIdentifier; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isAwsSDKSpan(span)) { const awsTableNames = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES]; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES) && Array.isArray(awsTableNames) && awsTableNames.length === 1) { remoteResourceType = NORMALIZED_DYNAMO_DB_SERVICE_NAME + '::Table'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(awsTableNames[0]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_NAME)) { remoteResourceType = NORMALIZED_KINESIS_SERVICE_NAME + '::Stream'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_KINESIS_STREAM_NAME]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_S3_BUCKET)) { remoteResourceType = NORMALIZED_S3_SERVICE_NAME + '::Bucket'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_S3_BUCKET]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN)) { const snsArn = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN]; remoteResourceType = NORMALIZED_SNS_SERVICE_NAME + '::Topic'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(this.extractResourceNameFromArn(snsArn)); cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(snsArn); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN)) { const secretsArn = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN]; remoteResourceType = NORMALIZED_SECRETSMANAGER_SERVICE_NAME + '::Secret'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(this.extractResourceNameFromArn(secretsArn)); cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(secretsArn); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN)) { const stateMachineArn = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN]; remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::StateMachine'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(this.extractResourceNameFromArn(stateMachineArn)); cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(stateMachineArn); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN)) { const activityArn = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN]; remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::Activity'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(this.extractResourceNameFromArn(activityArn)); cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(activityArn); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME)) { // Handling downstream Lambda as a service vs. an AWS resource: // - If the method call is "Invoke", we treat downstream Lambda as a service. // - Otherwise, we treat it as an AWS resource. // // This addresses a Lambda topology issue in Application Signals. // More context in PR: https://github.com/aws-observability/aws-otel-python-instrumentation/pull/319 // // NOTE: The env var LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT was introduced as part of this fix. // It is optional and allow users to override the default value if needed. if (AwsMetricAttributeGenerator.getRemoteOperation(span, semantic_conventions_1.SEMATTRS_RPC_METHOD) === 'Invoke') { attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE] = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME]); attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_ENVIRONMENT] = `lambda:${process.env.LAMBDA_APPLICATION_SIGNALS_REMOTE_ENVIRONMENT || 'default'}`; } else { remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::Function'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_NAME]); cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_FUNCTION_ARN]); } } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID)) { remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::EventSourceMapping'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME)) { remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL)) { const sqsQueueUrl = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL]); remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue'; remoteResourceIdentifier = sqs_url_parser_1.SqsUrlParser.getQueueName(sqsQueueUrl); cloudFormationIdentifier = sqsQueueUrl; } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Agent'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::DataSource'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]); cloudFormationIdentifier = `${AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID])}|${remoteResourceIdentifier}`; } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Guardrail'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]); cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::KnowledgeBase'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, aws_span_processing_util_1.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL)) { remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Model'; remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[aws_span_processing_util_1.AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]); } } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isDBSpan(span)) { remoteResourceType = DB_CONNECTION_RESOURCE_TYPE; remoteResourceIdentifier = AwsMetricAttributeGenerator.getDbConnection(span); } if (remoteResourceType !== undefined && remoteResourceIdentifier !== undefined) { attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE] = remoteResourceType; attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER] = remoteResourceIdentifier; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isAwsSDKSpan(span)) { if (cloudFormationIdentifier === undefined) { cloudFormationIdentifier = remoteResourceIdentifier; } attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier; } } } /** * RemoteResourceIdentifier is populated with rule <code> * ^[{db.name}|]?{address}[|{port}]? * </code> * * <pre> * {address} attribute is retrieved in priority order: * - {@link _SERVER_ADDRESS}, * - {@link SEMATTRS_NET_PEER_NAME}, * - {@link _SERVER_SOCKET_ADDRESS} * - {@link SEMATTRS_DB_CONNECTION_STRING}-Hostname * </pre> * * <pre> * {port} attribute is retrieved in priority order: * - {@link _SERVER_PORT}, * - {@link SEMATTRS_NET_PEER_PORT}, * - {@link _SERVER_SOCKET_PORT} * - {@link SEMATTRS_DB_CONNECTION_STRING}-Port * </pre> * * If address is not present, neither RemoteResourceType nor RemoteResourceIdentifier will be * provided. */ static getDbConnection(span) { const dbName = span.attributes[semantic_conventions_1.SEMATTRS_DB_NAME]; let dbConnection; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, _SERVER_ADDRESS)) { const serverAddress = span.attributes[_SERVER_ADDRESS]; const serverPort = span.attributes[_SERVER_PORT]; dbConnection = AwsMetricAttributeGenerator.buildDbConnection(serverAddress, serverPort); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_NET_PEER_NAME)) { const networkPeerAddress = span.attributes[semantic_conventions_1.SEMATTRS_NET_PEER_NAME]; const networkPeerPort = span.attributes[semantic_conventions_1.SEMATTRS_NET_PEER_PORT]; dbConnection = AwsMetricAttributeGenerator.buildDbConnection(networkPeerAddress, networkPeerPort); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, _SERVER_SOCKET_ADDRESS)) { const serverSocketAddress = span.attributes[_SERVER_SOCKET_ADDRESS]; const serverSocketPort = span.attributes[_SERVER_SOCKET_PORT]; dbConnection = AwsMetricAttributeGenerator.buildDbConnection(serverSocketAddress, serverSocketPort); } else if (aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_DB_CONNECTION_STRING)) { const connectionString = span.attributes[semantic_conventions_1.SEMATTRS_DB_CONNECTION_STRING]; dbConnection = AwsMetricAttributeGenerator.buildDbConnectionString(connectionString); } // return empty resource identifier if db server is not found if (dbConnection !== undefined && dbName !== undefined) { return AwsMetricAttributeGenerator.escapeDelimiters(dbName) + '|' + dbConnection; } return dbConnection; } static buildDbConnection(address, port) { if (typeof address !== 'string') { return undefined; } return AwsMetricAttributeGenerator.escapeDelimiters(address) + (port !== undefined ? '|' + port : ''); } static buildDbConnectionString(connectionString) { if (typeof connectionString !== 'string') { return undefined; } let uri; let address; let port; try { // Divergence from Java/Python // `jdbc:<dababase>://` isn't handled well with `new URL()` // uri.host and uri.port will be empty strings // examples: // - jdbc:postgresql://host:port/database?properties // - jdbc:mysql://localhost:3306 // - abc:def:ghi://host:3306 // Try with a dummy schema without `:`, since we do not care about the schema const schemeEndIndex = connectionString.indexOf('://'); if (schemeEndIndex === -1) { uri = new URL(connectionString); } else { uri = new URL('dummyschema' + connectionString.substring(schemeEndIndex)); } address = uri.hostname; port = uri.port; } catch (error) { api_1.diag.verbose(`invalid DB ConnectionString: ${connectionString}`); return undefined; } if (address === '') { return undefined; } return AwsMetricAttributeGenerator.escapeDelimiters(address) + (port !== '' ? '|' + port : ''); } static escapeDelimiters(input) { if (typeof input !== 'string') { return undefined; } // Divergence from Java/Python // `replaceAll(a,b)` is not available, and `replace(a,b)` only replaces the first occurrence // `split(a).join(b)` is not equivalent for all (a,b), but works with `a = '^'` or a = '|'`. // Implementing some regex is also possible // e.g. let re = new RegExp(String.raw`\s${variable}\s`, "g"); return input.split('^').join('^^').split('|').join('^|'); } // Extracts the name of the resource from an arn static extractResourceNameFromArn(attribute) { if (typeof attribute === 'string' && attribute.startsWith('arn:aws:')) { const split = attribute.split(':'); return split[split.length - 1]; } return undefined; } /** Span kind is needed for differentiating metrics in the EMF exporter */ static setSpanKindForService(span, attributes) { let spanKind = api_1.SpanKind[span.kind]; if (aws_span_processing_util_1.AwsSpanProcessingUtil.isLocalRoot(span)) { spanKind = aws_span_processing_util_1.AwsSpanProcessingUtil.LOCAL_ROOT; } attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SPAN_KIND] = spanKind; } static setSpanKindForDependency(span, attributes) { const spanKind = api_1.SpanKind[span.kind]; attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_SPAN_KIND] = spanKind; } static setRemoteDbUser(span, attributes) { if (aws_span_processing_util_1.AwsSpanProcessingUtil.isDBSpan(span) && aws_span_processing_util_1.AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_DB_USER)) { attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_DB_USER] = span.attributes[semantic_conventions_1.SEMATTRS_DB_USER]; } } static getRemoteService(span, remoteServiceKey) { let remoteService = span.attributes[remoteServiceKey]; if (typeof remoteService !== 'string') { remoteService = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE; } return remoteService; } static getRemoteOperation(span, remoteOperationKey) { let remoteOperation = span.attributes[remoteOperationKey]; if (typeof remoteOperation !== 'string') { remoteOperation = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION; } return remoteOperation; } /** * If no db.operation attribute provided in the span, we use db.statement to compute a valid * remote operation in a best-effort manner. To do this, we take the first substring of the * statement and compare to a regex list of known SQL keywords. The substring length is determined * by the longest known SQL keywords. */ static getDBStatementRemoteOperation(span, remoteOperationKey) { let remoteOperation = span.attributes[remoteOperationKey]; if (typeof remoteOperation !== 'string') { remoteOperation = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION; } // Remove all whitespace and newline characters from the beginning of remote_operation // and retrieve the first MAX_KEYWORD_LENGTH characters remoteOperation = remoteOperation.trimStart(); if (remoteOperation.length > aws_span_processing_util_1.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH) { remoteOperation = remoteOperation.substring(0, aws_span_processing_util_1.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH); } const matcher = remoteOperation .toUpperCase() .match(aws_span_processing_util_1.AwsSpanProcessingUtil.SQL_DIALECT_PATTERN); if (matcher == null || matcher.length === 0) { remoteOperation = aws_span_processing_util_1.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION; } else { remoteOperation = matcher[0]; } return remoteOperation; } static logUnknownAttribute(attributeKey, span) { api_1.diag.verbose(`No valid ${attributeKey} value found for ${api_1.SpanKind[span.kind]} span ${span.spanContext().spanId}`); } } exports.AwsMetricAttributeGenerator = AwsMetricAttributeGenerator; //# sourceMappingURL=aws-metric-attribute-generator.js.map