UNPKG

enhanced-adot-node-autoinstrumentation

Version:

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

250 lines 14.1 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.AwsSpanProcessingUtil = void 0; const api_1 = require("@opentelemetry/api"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const aws_attribute_keys_1 = require("./aws-attribute-keys"); const aws_opentelemetry_configurator_1 = require("./aws-opentelemetry-configurator"); const SQL_DIALECT_KEYWORDS_JSON = require("./configuration/sql_dialect_keywords.json"); /** Utility class designed to support shared logic across AWS Span Processors. */ class AwsSpanProcessingUtil { static getDialectKeywords() { return SQL_DIALECT_KEYWORDS_JSON.keywords; } /** * 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 getIngressOperation(span) { let operation = span.name; if (AwsSpanProcessingUtil.shouldUseInternalOperation(span)) { operation = AwsSpanProcessingUtil.INTERNAL_OPERATION; } if ((0, aws_opentelemetry_configurator_1.isLambdaEnvironment)()) { operation = process.env[aws_opentelemetry_configurator_1.AWS_LAMBDA_FUNCTION_NAME_CONFIG] + '/FunctionHandler'; } else if (!AwsSpanProcessingUtil.isValidOperation(span, operation)) { operation = AwsSpanProcessingUtil.generateIngressOperation(span); } return operation; } static getEgressOperation(span) { if (AwsSpanProcessingUtil.shouldUseInternalOperation(span)) { return AwsSpanProcessingUtil.INTERNAL_OPERATION; } else { const awsLocalOperation = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_LOCAL_OPERATION]; return awsLocalOperation === undefined ? undefined : awsLocalOperation.toString(); } } /** * Extract the first part from API http target if it exists * * @param httpTarget http request target string value. Eg, /payment/1234 * @return the first part from the http target. Eg, /payment */ static extractAPIPathValue(httpTarget) { // In TypeScript, `httpTarget == null` checks both null and undefined if (httpTarget == null || httpTarget === '') { return '/'; } // Divergence from Java/Python // https://github.com/open-telemetry/semantic-conventions/blob/4e7c42ee8e4c3a39a899c4c85c64df28cd543f78/docs/attributes-registry/http.md#deprecated-http-attributes // According to OTel Spec, httpTarget may include query and fragment: // - `/search?q=OpenTelemetry#SemConv` // We do NOT want the `?` or `#` parts, so let us strip it out, // because HTTP (ingress) instrumentation was observed to include the query (`?`) part // - https://github.com/open-telemetry/opentelemetry-js/blob/b418d36609c371d1fcae46898e9ede6278aca917/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts#L502-L504 // According to RFC Specification, "The path is terminated by the first question mark ("?") or number sign ("#") character, or by the end of the URI." // - https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 // // This is a fix that can be applied here since this is the central location for generating API Path Value // TODO: Possibly contribute fix to upstream for this diff between langauges. However, the current attribute value in JS is according to spec. // // Interestingly, according to Spec, Java/Python should be affected, but they are not. const paths = httpTarget.split(/[/?#]/); if (paths.length > 1) { return '/' + paths[1]; } return '/'; } static isKeyPresent(span, key) { return span.attributes[key] !== undefined; } static isAwsSDKSpan(span) { const rpcSystem = span.attributes[semantic_conventions_1.SEMATTRS_RPC_SYSTEM]; if (rpcSystem === undefined) { return false; } // https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/instrumentation/aws-sdk/#common-attributes return 'aws-api' === rpcSystem; } static shouldGenerateServiceMetricAttributes(span) { return ((AwsSpanProcessingUtil.isLocalRoot(span) && !AwsSpanProcessingUtil.isSqsReceiveMessageConsumerSpan(span)) || api_1.SpanKind.SERVER === span.kind); } static shouldGenerateDependencyMetricAttributes(span) { return (api_1.SpanKind.CLIENT === span.kind || api_1.SpanKind.PRODUCER === span.kind || (AwsSpanProcessingUtil.isDependencyConsumerSpan(span) && !AwsSpanProcessingUtil.isSqsReceiveMessageConsumerSpan(span))); } static isConsumerProcessSpan(spanData) { const messagingOperation = spanData.attributes[semantic_conventions_1.SEMATTRS_MESSAGING_OPERATION]; if (messagingOperation === undefined) { return false; } return api_1.SpanKind.CONSUMER === spanData.kind && semantic_conventions_1.MessagingOperationValues.PROCESS === messagingOperation; } // Any spans that are Local Roots and also not SERVER should have aws.local.operation renamed to // InternalOperation. static shouldUseInternalOperation(span) { return AwsSpanProcessingUtil.isLocalRoot(span) && api_1.SpanKind.SERVER !== span.kind; } // A span is a local root if it has no parent or if the parent is remote. This function checks the // parent context and returns true if it is a local root. static isLocalRoot(spanData) { // Workaround implemented for this function as parent span context is not obtainable. // This isLocalRoot value is precalculated in AttributePropagatingSpanProcessor, which // is started before the other processors (e.g. AwsSpanMetricsProcessor) // Thus this function is implemented differently than in Java/Python const isLocalRoot = spanData.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT]; if (typeof isLocalRoot !== 'boolean') { // isLocalRoot should be a precalculated boolean, this code block should not be entered api_1.diag.debug('isLocalRoot for span has not been precalculated. Assuming span is Local Root Span.'); return true; } return isLocalRoot; } // To identify the SQS consumer spans produced by AWS SDK instrumentation static isSqsReceiveMessageConsumerSpan(spanData) { const spanName = spanData.name; const spanKind = spanData.kind; const messagingOperation = spanData.attributes[semantic_conventions_1.SEMATTRS_MESSAGING_OPERATION]; const instrumentationLibrary = spanData.instrumentationLibrary; return (AwsSpanProcessingUtil.SQS_RECEIVE_MESSAGE_SPAN_NAME.toLowerCase() === spanName.toLowerCase() && api_1.SpanKind.CONSUMER === spanKind && instrumentationLibrary != null && instrumentationLibrary.name.startsWith(AwsSpanProcessingUtil.AWS_SDK_INSTRUMENTATION_SCOPE_PREFIX) && (messagingOperation === undefined || messagingOperation === semantic_conventions_1.MessagingOperationValues.PROCESS)); } static isDependencyConsumerSpan(span) { if (api_1.SpanKind.CONSUMER !== span.kind) { return false; } else if (AwsSpanProcessingUtil.isConsumerProcessSpan(span)) { if (AwsSpanProcessingUtil.isLocalRoot(span)) { return true; } const parentSpanKind = span.attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_CONSUMER_PARENT_SPAN_KIND]; return api_1.SpanKind[api_1.SpanKind.CONSUMER] !== parentSpanKind; } return true; } /** * When Span name is null, UnknownOperation or HttpMethod value, it will be treated as invalid * local operation value that needs to be further processed */ static isValidOperation(span, operation) { if (operation == null || operation === AwsSpanProcessingUtil.UNKNOWN_OPERATION) { return false; } if (AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_HTTP_METHOD)) { const httpMethod = span.attributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD]; return operation !== httpMethod; } return true; } /** * When span name is not meaningful(null, unknown or http_method value) as operation name for http * use cases. Will try to extract the operation name from http target string */ static generateIngressOperation(span) { let operation = AwsSpanProcessingUtil.UNKNOWN_OPERATION; let httpPath = undefined; if (AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_HTTP_TARGET)) { httpPath = span.attributes[semantic_conventions_1.SEMATTRS_HTTP_TARGET]; } else if (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); httpPath = url.pathname; } } catch (e) { // In Python, if `httpUrl == ''`, there is no error from URL parsing, and `url.pathname = ''` // In TypeScript, this catch block will be invoked. Here `httpPath = ''` is set as default to match Python. api_1.diag.verbose(`invalid http.url attribute: ${httpUrl}, setting httpPath as empty string`); httpPath = ''; } } if (typeof httpPath === 'string') { operation = this.extractAPIPathValue(httpPath); if (this.isKeyPresent(span, semantic_conventions_1.SEMATTRS_HTTP_METHOD)) { const httpMethod = span.attributes[semantic_conventions_1.SEMATTRS_HTTP_METHOD]; if (httpMethod !== undefined) { operation = httpMethod + ' ' + operation; } } } return operation; } // Check if the current Span adheres to database semantic conventions static isDBSpan(span) { return (AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_DB_SYSTEM) || AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_DB_OPERATION) || AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMATTRS_DB_STATEMENT)); } // Divergence from Java/Python static setIsLocalRootInformation(span, parentContext) { const parentSpanContext = api_1.trace.getSpanContext(parentContext); const isParentSpanContextValid = parentSpanContext !== undefined && (0, api_1.isSpanContextValid)(parentSpanContext); const isParentSpanRemote = parentSpanContext !== undefined && parentSpanContext.isRemote === true; const isLocalRoot = span.parentSpanId === undefined || !isParentSpanContextValid || isParentSpanRemote; span.setAttribute(aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_IS_LOCAL_ROOT, isLocalRoot); } static getResourceId(span) { let resourceId = undefined; if (AwsSpanProcessingUtil.isKeyPresent(span, AwsSpanProcessingUtil.CLOUD_RESOURCE_ID)) { resourceId = span.attributes[AwsSpanProcessingUtil.CLOUD_RESOURCE_ID]; } else if (AwsSpanProcessingUtil.isKeyPresent(span, semantic_conventions_1.SEMRESATTRS_FAAS_ID)) { resourceId = span.attributes[semantic_conventions_1.SEMRESATTRS_FAAS_ID]; } return typeof resourceId === 'string' ? resourceId : undefined; } } exports.AwsSpanProcessingUtil = AwsSpanProcessingUtil; // Default attribute values if no valid span attribute value is identified AwsSpanProcessingUtil.UNKNOWN_SERVICE = 'UnknownService'; AwsSpanProcessingUtil.UNKNOWN_OPERATION = 'UnknownOperation'; AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE = 'UnknownRemoteService'; AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION = 'UnknownRemoteOperation'; AwsSpanProcessingUtil.INTERNAL_OPERATION = 'InternalOperation'; AwsSpanProcessingUtil.LOCAL_ROOT = 'LOCAL_ROOT'; AwsSpanProcessingUtil.SQS_RECEIVE_MESSAGE_SPAN_NAME = 'Sqs.ReceiveMessage'; AwsSpanProcessingUtil.AWS_SDK_INSTRUMENTATION_SCOPE_PREFIX = '@opentelemetry/instrumentation-aws-sdk'; // "cloud.resource_id" is defined in semconv which has not yet picked up by OTel JS // https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/ AwsSpanProcessingUtil.CLOUD_RESOURCE_ID = 'cloud.resource_id'; // Max keyword length supported by parsing into remote_operation from DB_STATEMENT. // The current longest command word is DATETIME_INTERVAL_PRECISION at 27 characters. // If we add a longer keyword to the sql dialect keyword list, need to update the constant below. AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH = 27; AwsSpanProcessingUtil.SQL_DIALECT_PATTERN = '^(?:' + AwsSpanProcessingUtil.getDialectKeywords().join('|') + ')\\b'; // TODO: Use Semantic Conventions once upgraded AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL = 'gen_ai.request.model'; AwsSpanProcessingUtil.GEN_AI_SYSTEM = 'gen_ai.system'; AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS = 'gen_ai.request.max_tokens'; AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE = 'gen_ai.request.temperature'; AwsSpanProcessingUtil.GEN_AI_REQUEST_TOP_P = 'gen_ai.request.top_p'; AwsSpanProcessingUtil.GEN_AI_RESPONSE_FINISH_REASONS = 'gen_ai.response.finish_reasons'; AwsSpanProcessingUtil.GEN_AI_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens'; AwsSpanProcessingUtil.GEN_AI_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens'; //# sourceMappingURL=aws-span-processing-util.js.map