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
JavaScript
;
// 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