Skip to content

Commit

Permalink
Merge branch 'aws-observability:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
harrryr authored Dec 17, 2024
2 parents 04a5ffc + fec9390 commit 87ec407
Show file tree
Hide file tree
Showing 26 changed files with 2,715 additions and 281 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ RUN npm install aws-aws-distro-opentelemetry-node-autoinstrumentation-$(node -p
RUN npm install

# Stage 2: Build the cp-utility binary
FROM public.ecr.aws/docker/library/rust:1.75 as builder
FROM public.ecr.aws/docker/library/rust:1.81 as builder

WORKDIR /usr/src/cp-utility
COPY ./tools/cp-utility .
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build
node_modules
.eslintrc.js
version.ts
version.ts
src/third-party
15 changes: 14 additions & 1 deletion aws-distro-opentelemetry-node-autoinstrumentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,18 @@
"prepublishOnly": "npm run compile",
"tdd": "yarn test -- --watch-extensions ts --watch",
"test": "nyc ts-mocha --timeout 10000 -p tsconfig.json --require '@opentelemetry/contrib-test-utils' 'test/**/*.ts'",
"test:coverage": "nyc --all --check-coverage --functions 95 --lines 95 ts-mocha --timeout 10000 -p tsconfig.json --require '@opentelemetry/contrib-test-utils' 'test/**/*.ts'",
"test:coverage": "nyc --check-coverage --functions 95 --lines 95 ts-mocha --timeout 10000 -p tsconfig.json --require '@opentelemetry/contrib-test-utils' 'test/**/*.ts'",
"watch": "tsc -w"
},
"nyc": {
"all": true,
"include": [
"src/**/*.ts"
],
"exclude": [
"src/third-party/**/*.ts"
]
},
"bugs": {
"url": "https://github.com/aws-observability/aws-otel-js-instrumentation/issues"
},
Expand Down Expand Up @@ -64,7 +73,11 @@
"@aws-sdk/client-bedrock-agent-runtime": "3.632.0",
"@aws-sdk/client-bedrock-runtime": "3.632.0",
"@aws-sdk/client-kinesis": "3.632.0",
"@aws-sdk/client-lambda": "^3.632.0",
"@aws-sdk/client-s3": "3.632.0",
"@aws-sdk/client-secrets-manager": "^3.632.0",
"@aws-sdk/client-sfn": "^3.632.0",
"@aws-sdk/client-sns": "^3.632.0",
"@opentelemetry/contrib-test-utils": "0.41.0",
"@types/mocha": "7.0.2",
"@types/node": "18.6.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = {
AWS_REMOTE_RESOURCE_IDENTIFIER: 'aws.remote.resource.identifier',
AWS_SDK_DESCENDANT: 'aws.sdk.descendant',
AWS_CONSUMER_PARENT_SPAN_KIND: 'aws.consumer.parent.span.kind',
AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER: 'aws.remote.resource.cfn.primary.identifier',

AWS_REMOTE_TARGET: 'aws.remote.target',
AWS_REMOTE_DB_USER: 'aws.remote.db.user',
Expand All @@ -35,4 +36,12 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = {
AWS_BEDROCK_KNOWLEDGE_BASE_ID: 'aws.bedrock.knowledge_base.id',
AWS_BEDROCK_AGENT_ID: 'aws.bedrock.agent.id',
AWS_BEDROCK_GUARDRAIL_ID: 'aws.bedrock.guardrail.id',
AWS_BEDROCK_GUARDRAIL_ARN: 'aws.bedrock.guardrail.arn',
AWS_SNS_TOPIC_ARN: 'aws.sns.topic.arn',
AWS_SECRETSMANAGER_SECRET_ARN: 'aws.secretsmanager.secret.arn',
AWS_STEPFUNCTIONS_STATEMACHINE_ARN: 'aws.stepfunctions.state_machine.arn',
AWS_STEPFUNCTIONS_ACTIVITY_ARN: 'aws.stepfunctions.activity.arn',
AWS_LAMBDA_FUNCTION_NAME: 'aws.lambda.function.name',
AWS_LAMBDA_RESOURCE_MAPPING_ID: 'aws.lambda.resource_mapping.id',
AWS_LAMBDA_FUNCTION_ARN: 'aws.lambda.function.arn',
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const NORMALIZED_DYNAMO_DB_SERVICE_NAME: string = 'AWS::DynamoDB';
const NORMALIZED_KINESIS_SERVICE_NAME: string = 'AWS::Kinesis';
const NORMALIZED_S3_SERVICE_NAME: string = 'AWS::S3';
const NORMALIZED_SQS_SERVICE_NAME: string = 'AWS::SQS';
const NORMALIZED_SNS_SERVICE_NAME: string = '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: string = 'AWS::Bedrock';
const NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: string = 'AWS::BedrockRuntime';

Expand Down Expand Up @@ -330,6 +334,8 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
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;
}
Expand All @@ -350,6 +356,7 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
private static setRemoteResourceTypeAndIdentifier(span: ReadableSpan, attributes: Attributes): void {
let remoteResourceType: AttributeValue | undefined;
let remoteResourceIdentifier: AttributeValue | undefined;
let cloudFormationIdentifier: AttributeValue | undefined;

if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
const awsTableNames: AttributeValue | undefined = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES];
Expand All @@ -370,16 +377,56 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_S3_BUCKET]
);
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN)) {
const snsArn = span.attributes[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 (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN)) {
const secretsArn = span.attributes[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 (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN)) {
const stateMachineArn = span.attributes[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 (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN)) {
const activityArn = span.attributes[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 (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID)) {
remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::EventSourceMapping';
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID]
);
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME)) {
remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue';
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]
);
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL)) {
remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue';
remoteResourceIdentifier = SqsUrlParser.getQueueName(
AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL])
const sqsQueueUrl = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL]
);

remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue';
remoteResourceIdentifier = SqsUrlParser.getQueueName(sqsQueueUrl);
cloudFormationIdentifier = sqsQueueUrl;
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID)) {
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Agent';
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
Expand All @@ -390,11 +437,17 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]
);
cloudFormationIdentifier = `${AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]
)}|${remoteResourceIdentifier}`;
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID)) {
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Guardrail';
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]
);
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN]
);
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID)) {
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::KnowledgeBase';
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
Expand All @@ -414,6 +467,14 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
if (remoteResourceType !== undefined && remoteResourceIdentifier !== undefined) {
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE] = remoteResourceType;
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER] = remoteResourceIdentifier;

if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
if (cloudFormationIdentifier === undefined) {
cloudFormationIdentifier = remoteResourceIdentifier;
}

attributes[AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier;
}
}
}

Expand Down Expand Up @@ -532,6 +593,16 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
return input.split('^').join('^^').split('|').join('^|');
}

// Extracts the name of the resource from an arn
private static extractResourceNameFromArn(attribute: AttributeValue | undefined): string | undefined {
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 */
private static setSpanKindForService(span: ReadableSpan, attributes: Attributes): void {
let spanKind: string = SpanKind[span.kind];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const AGENT_ID: string = 'agentId';
const KNOWLEDGE_BASE_ID: string = 'knowledgeBaseId';
const DATA_SOURCE_ID: string = 'dataSourceId';
const GUARDRAIL_ID: string = 'guardrailId';
const GUARDRAIL_ARN: string = 'guardrailArn';
const MODEL_ID: string = 'modelId';
const AWS_BEDROCK_SYSTEM: string = 'aws_bedrock';
const AWS_BEDROCK_SYSTEM: string = 'aws.bedrock';

const AGENT_OPERATIONS = [
'CreateAgentActionGroup',
Expand Down Expand Up @@ -60,6 +61,7 @@ const knowledgeBaseOperationAttributeKeyMapping = {
};
const dataSourceOperationAttributeKeyMapping = {
[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]: DATA_SOURCE_ID,
[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]: KNOWLEDGE_BASE_ID,
};

// This map allows us to get all relevant attribute key mappings for a given operation
Expand Down Expand Up @@ -182,10 +184,15 @@ export class BedrockServiceExtension implements ServiceExtension {
};
}
responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void {
const guardrail_id = response.data[GUARDRAIL_ID];
const guardrailId = response.data[GUARDRAIL_ID];
const guardrailArn = response.data[GUARDRAIL_ARN];

if (guardrailId) {
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrailId);
}

if (guardrail_id) {
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrail_id);
if (guardrailArn) {
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN, guardrailArn);
}
}
}
Expand Down Expand Up @@ -225,6 +232,16 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension {
spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS] =
requestBody.textGenerationConfig.maxTokenCount;
}
} else if (modelId.includes('amazon.nova')) {
if (requestBody.inferenceConfig?.temperature !== undefined) {
spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TEMPERATURE] = requestBody.inferenceConfig.temperature;
}
if (requestBody.inferenceConfig?.top_p !== undefined) {
spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_TOP_P] = requestBody.inferenceConfig.top_p;
}
if (requestBody.inferenceConfig?.max_new_tokens !== undefined) {
spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS] = requestBody.inferenceConfig.max_new_tokens;
}
} else if (modelId.includes('anthropic.claude')) {
if (requestBody.max_tokens !== undefined) {
spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_tokens;
Expand Down Expand Up @@ -328,6 +345,18 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension {
responseBody.results[0].completionReason,
]);
}
} else if (currentModelId.includes('amazon.nova')) {
if (responseBody.usage !== undefined) {
if (responseBody.usage.inputTokens !== undefined) {
span.setAttribute(AwsSpanProcessingUtil.GEN_AI_USAGE_INPUT_TOKENS, responseBody.usage.inputTokens);
}
if (responseBody.usage.outputTokens !== undefined) {
span.setAttribute(AwsSpanProcessingUtil.GEN_AI_USAGE_OUTPUT_TOKENS, responseBody.usage.outputTokens);
}
}
if (responseBody.stopReason !== undefined) {
span.setAttribute(AwsSpanProcessingUtil.GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.stopReason]);
}
} else if (currentModelId.includes('anthropic.claude')) {
if (responseBody.usage?.input_tokens !== undefined) {
span.setAttribute(AwsSpanProcessingUtil.GEN_AI_USAGE_INPUT_TOKENS, responseBody.usage.input_tokens);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api';
import {
AwsSdkInstrumentationConfig,
NormalizedRequest,
NormalizedResponse,
} from '@opentelemetry/instrumentation-aws-sdk';
import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys';
import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension';

export class SecretsManagerServiceExtension implements ServiceExtension {
requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata {
const secretId = request.commandInput?.SecretId;

const spanKind: SpanKind = SpanKind.CLIENT;
let spanName: string | undefined;

const spanAttributes: Attributes = {};

if (typeof secretId === 'string' && secretId.startsWith('arn:aws:secretsmanager:')) {
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN] = secretId;
}

const isIncoming = false;

return {
isIncoming,
spanAttributes,
spanKind,
spanName,
};
}

responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void {
const secretArn = response.data.ARN;

if (secretArn) {
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN, secretArn);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { Attributes, SpanKind } from '@opentelemetry/api';
import { AwsSdkInstrumentationConfig, NormalizedRequest } from '@opentelemetry/instrumentation-aws-sdk';
import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys';
import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension';

export class StepFunctionsServiceExtension implements ServiceExtension {
requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata {
const stateMachineArn = request.commandInput?.stateMachineArn;
const activityArn = request.commandInput?.activityArn;

const spanKind: SpanKind = SpanKind.CLIENT;
let spanName: string | undefined;

const spanAttributes: Attributes = {};

if (stateMachineArn) {
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN] = stateMachineArn;
}

if (activityArn) {
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN] = activityArn;
}

const isIncoming = false;

return {
isIncoming,
spanAttributes,
spanKind,
spanName,
};
}
}
Loading

0 comments on commit 87ec407

Please sign in to comment.