Skip to content

Commit c58112e

Browse files
authored
feat: [.NET and JS Feature Parity] Support for new AWS Resources in JS SDK (#121)
*Description of changes:* Adding support for new AWS resources in JS SDK. Part of an ongoing project to increase the ADOT SDK support in Node and .NET. Changes in this PR support the exact same features as the Python version: aws-observability/aws-otel-python-instrumentation#265 Manual Testing: <img width="1236" alt="Screenshot 2024-11-15 at 1 36 01 PM" src="https://github.com/user-attachments/assets/7480f865-2927-4376-91b5-76048b0bcdf4"> <img width="1234" alt="Screenshot 2024-11-15 at 2 07 05 PM" src="https://github.com/user-attachments/assets/1a5d00b4-9ac8-45e9-b51d-02d7c669f5f1"> <img width="939" alt="Screenshot 2024-11-15 at 2 26 10 PM" src="https://github.com/user-attachments/assets/120892ec-03cf-4b70-80c7-167369cd9e27"> <img width="996" alt="Screenshot 2024-11-18 at 9 47 33 AM" src="https://github.com/user-attachments/assets/08019f4c-507c-46cf-baa0-db77a4175d02"> <img width="934" alt="Screenshot 2024-11-18 at 9 57 22 AM" src="https://github.com/user-attachments/assets/3fb79fa9-689f-4361-b671-79e23c68193d"> <img width="852" alt="Screenshot 2024-11-18 at 10 00 34 AM" src="https://github.com/user-attachments/assets/bde63f88-cdbc-43c9-aa16-068790556ebe"> Unit Tests for Instrumentation Patches and Metric Attribute Generators: <img width="1070" alt="image" src="https://github.com/user-attachments/assets/df01814c-49fb-4561-879b-c88cedb35e5c"> By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent a9375ff commit c58112e

File tree

14 files changed

+1641
-225
lines changed

14 files changed

+1641
-225
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@
7373
"@aws-sdk/client-bedrock-agent-runtime": "3.632.0",
7474
"@aws-sdk/client-bedrock-runtime": "3.632.0",
7575
"@aws-sdk/client-kinesis": "3.632.0",
76+
"@aws-sdk/client-lambda": "^3.632.0",
7677
"@aws-sdk/client-s3": "3.632.0",
78+
"@aws-sdk/client-secrets-manager": "^3.632.0",
79+
"@aws-sdk/client-sfn": "^3.632.0",
80+
"@aws-sdk/client-sns": "^3.632.0",
7781
"@opentelemetry/contrib-test-utils": "0.41.0",
7882
"@types/mocha": "7.0.2",
7983
"@types/node": "18.6.5",

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = {
1414
AWS_REMOTE_RESOURCE_IDENTIFIER: 'aws.remote.resource.identifier',
1515
AWS_SDK_DESCENDANT: 'aws.sdk.descendant',
1616
AWS_CONSUMER_PARENT_SPAN_KIND: 'aws.consumer.parent.span.kind',
17+
AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER: 'aws.remote.resource.cfn.primary.identifier',
1718

1819
AWS_REMOTE_TARGET: 'aws.remote.target',
1920
AWS_REMOTE_DB_USER: 'aws.remote.db.user',
@@ -35,4 +36,12 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = {
3536
AWS_BEDROCK_KNOWLEDGE_BASE_ID: 'aws.bedrock.knowledge_base.id',
3637
AWS_BEDROCK_AGENT_ID: 'aws.bedrock.agent.id',
3738
AWS_BEDROCK_GUARDRAIL_ID: 'aws.bedrock.guardrail.id',
39+
AWS_BEDROCK_GUARDRAIL_ARN: 'aws.bedrock.guardrail.arn',
40+
AWS_SNS_TOPIC_ARN: 'aws.sns.topic.arn',
41+
AWS_SECRETSMANAGER_SECRET_ARN: 'aws.secretsmanager.secret.arn',
42+
AWS_STEPFUNCTIONS_STATEMACHINE_ARN: 'aws.stepfunctions.state_machine.arn',
43+
AWS_STEPFUNCTIONS_ACTIVITY_ARN: 'aws.stepfunctions.activity.arn',
44+
AWS_LAMBDA_FUNCTION_NAME: 'aws.lambda.function.name',
45+
AWS_LAMBDA_RESOURCE_MAPPING_ID: 'aws.lambda.resource_mapping.id',
46+
AWS_LAMBDA_FUNCTION_ARN: 'aws.lambda.function.arn',
3847
};

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ const NORMALIZED_DYNAMO_DB_SERVICE_NAME: string = 'AWS::DynamoDB';
5555
const NORMALIZED_KINESIS_SERVICE_NAME: string = 'AWS::Kinesis';
5656
const NORMALIZED_S3_SERVICE_NAME: string = 'AWS::S3';
5757
const NORMALIZED_SQS_SERVICE_NAME: string = 'AWS::SQS';
58+
const NORMALIZED_SNS_SERVICE_NAME: string = 'AWS::SNS';
59+
const NORMALIZED_SECRETSMANAGER_SERVICE_NAME = 'AWS::SecretsManager';
60+
const NORMALIZED_STEPFUNCTIONS_SERVICE_NAME = 'AWS::StepFunctions';
61+
const NORMALIZED_LAMBDA_SERVICE_NAME = 'AWS::Lambda';
5862
const NORMALIZED_BEDROCK_SERVICE_NAME: string = 'AWS::Bedrock';
5963
const NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: string = 'AWS::BedrockRuntime';
6064

@@ -330,6 +334,8 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
330334
BedrockAgent: NORMALIZED_BEDROCK_SERVICE_NAME,
331335
BedrockAgentRuntime: NORMALIZED_BEDROCK_SERVICE_NAME,
332336
BedrockRuntime: NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
337+
SecretsManager: NORMALIZED_SECRETSMANAGER_SERVICE_NAME,
338+
SFN: NORMALIZED_STEPFUNCTIONS_SERVICE_NAME,
333339
};
334340
return awsSdkServiceMapping[serviceName] || 'AWS::' + serviceName;
335341
}
@@ -350,6 +356,7 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
350356
private static setRemoteResourceTypeAndIdentifier(span: ReadableSpan, attributes: Attributes): void {
351357
let remoteResourceType: AttributeValue | undefined;
352358
let remoteResourceIdentifier: AttributeValue | undefined;
359+
let cloudFormationIdentifier: AttributeValue | undefined;
353360

354361
if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
355362
const awsTableNames: AttributeValue | undefined = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_DYNAMODB_TABLE_NAMES];
@@ -370,16 +377,56 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
370377
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
371378
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_S3_BUCKET]
372379
);
380+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN)) {
381+
const snsArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN];
382+
383+
remoteResourceType = NORMALIZED_SNS_SERVICE_NAME + '::Topic';
384+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
385+
this.extractResourceNameFromArn(snsArn)
386+
);
387+
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(snsArn);
388+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN)) {
389+
const secretsArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN];
390+
391+
remoteResourceType = NORMALIZED_SECRETSMANAGER_SERVICE_NAME + '::Secret';
392+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
393+
this.extractResourceNameFromArn(secretsArn)
394+
);
395+
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(secretsArn);
396+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN)) {
397+
const stateMachineArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN];
398+
399+
remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::StateMachine';
400+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
401+
this.extractResourceNameFromArn(stateMachineArn)
402+
);
403+
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(stateMachineArn);
404+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN)) {
405+
const activityArn = span.attributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN];
406+
407+
remoteResourceType = NORMALIZED_STEPFUNCTIONS_SERVICE_NAME + '::Activity';
408+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
409+
this.extractResourceNameFromArn(activityArn)
410+
);
411+
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(activityArn);
412+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID)) {
413+
remoteResourceType = NORMALIZED_LAMBDA_SERVICE_NAME + '::EventSourceMapping';
414+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
415+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID]
416+
);
373417
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME)) {
374418
remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue';
375419
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
376420
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_NAME]
377421
);
378422
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL)) {
379-
remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue';
380-
remoteResourceIdentifier = SqsUrlParser.getQueueName(
381-
AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL])
423+
const sqsQueueUrl = AwsMetricAttributeGenerator.escapeDelimiters(
424+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL]
382425
);
426+
427+
remoteResourceType = NORMALIZED_SQS_SERVICE_NAME + '::Queue';
428+
remoteResourceIdentifier = SqsUrlParser.getQueueName(sqsQueueUrl);
429+
cloudFormationIdentifier = sqsQueueUrl;
383430
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID)) {
384431
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Agent';
385432
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
@@ -390,11 +437,17 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
390437
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
391438
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]
392439
);
440+
cloudFormationIdentifier = `${AwsMetricAttributeGenerator.escapeDelimiters(
441+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]
442+
)}|${remoteResourceIdentifier}`;
393443
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID)) {
394444
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Guardrail';
395445
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
396446
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]
397447
);
448+
cloudFormationIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
449+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN]
450+
);
398451
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID)) {
399452
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::KnowledgeBase';
400453
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
@@ -414,6 +467,14 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
414467
if (remoteResourceType !== undefined && remoteResourceIdentifier !== undefined) {
415468
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_TYPE] = remoteResourceType;
416469
attributes[AWS_ATTRIBUTE_KEYS.AWS_REMOTE_RESOURCE_IDENTIFIER] = remoteResourceIdentifier;
470+
471+
if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
472+
if (cloudFormationIdentifier === undefined) {
473+
cloudFormationIdentifier = remoteResourceIdentifier;
474+
}
475+
476+
attributes[AWS_ATTRIBUTE_KEYS.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER] = cloudFormationIdentifier;
477+
}
417478
}
418479
}
419480

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

596+
// Extracts the name of the resource from an arn
597+
private static extractResourceNameFromArn(attribute: AttributeValue | undefined): string | undefined {
598+
if (typeof attribute === 'string' && attribute.startsWith('arn:aws:')) {
599+
const split = attribute.split(':');
600+
return split[split.length - 1];
601+
}
602+
603+
return undefined;
604+
}
605+
535606
/** Span kind is needed for differentiating metrics in the EMF exporter */
536607
private static setSpanKindForService(span: ReadableSpan, attributes: Attributes): void {
537608
let spanKind: string = SpanKind[span.kind];

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/aws/services/bedrock.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const AGENT_ID: string = 'agentId';
1515
const KNOWLEDGE_BASE_ID: string = 'knowledgeBaseId';
1616
const DATA_SOURCE_ID: string = 'dataSourceId';
1717
const GUARDRAIL_ID: string = 'guardrailId';
18+
const GUARDRAIL_ARN: string = 'guardrailArn';
1819
const MODEL_ID: string = 'modelId';
1920
const AWS_BEDROCK_SYSTEM: string = 'aws.bedrock';
2021

@@ -60,6 +61,7 @@ const knowledgeBaseOperationAttributeKeyMapping = {
6061
};
6162
const dataSourceOperationAttributeKeyMapping = {
6263
[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]: DATA_SOURCE_ID,
64+
[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]: KNOWLEDGE_BASE_ID,
6365
};
6466

6567
// This map allows us to get all relevant attribute key mappings for a given operation
@@ -182,10 +184,15 @@ export class BedrockServiceExtension implements ServiceExtension {
182184
};
183185
}
184186
responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void {
185-
const guardrail_id = response.data[GUARDRAIL_ID];
187+
const guardrailId = response.data[GUARDRAIL_ID];
188+
const guardrailArn = response.data[GUARDRAIL_ARN];
189+
190+
if (guardrailId) {
191+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrailId);
192+
}
186193

187-
if (guardrail_id) {
188-
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrail_id);
194+
if (guardrailArn) {
195+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ARN, guardrailArn);
189196
}
190197
}
191198
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api';
5+
import {
6+
AwsSdkInstrumentationConfig,
7+
NormalizedRequest,
8+
NormalizedResponse,
9+
} from '@opentelemetry/instrumentation-aws-sdk';
10+
import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys';
11+
import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension';
12+
13+
export class SecretsManagerServiceExtension implements ServiceExtension {
14+
requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata {
15+
const secretId = request.commandInput?.SecretId;
16+
17+
const spanKind: SpanKind = SpanKind.CLIENT;
18+
let spanName: string | undefined;
19+
20+
const spanAttributes: Attributes = {};
21+
22+
if (typeof secretId === 'string' && secretId.startsWith('arn:aws:secretsmanager:')) {
23+
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN] = secretId;
24+
}
25+
26+
const isIncoming = false;
27+
28+
return {
29+
isIncoming,
30+
spanAttributes,
31+
spanKind,
32+
spanName,
33+
};
34+
}
35+
36+
responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void {
37+
const secretArn = response.data.ARN;
38+
39+
if (secretArn) {
40+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_SECRETSMANAGER_SECRET_ARN, secretArn);
41+
}
42+
}
43+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Attributes, SpanKind } from '@opentelemetry/api';
5+
import { AwsSdkInstrumentationConfig, NormalizedRequest } from '@opentelemetry/instrumentation-aws-sdk';
6+
import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys';
7+
import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension';
8+
9+
export class StepFunctionsServiceExtension implements ServiceExtension {
10+
requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata {
11+
const stateMachineArn = request.commandInput?.stateMachineArn;
12+
const activityArn = request.commandInput?.activityArn;
13+
14+
const spanKind: SpanKind = SpanKind.CLIENT;
15+
let spanName: string | undefined;
16+
17+
const spanAttributes: Attributes = {};
18+
19+
if (stateMachineArn) {
20+
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_STATEMACHINE_ARN] = stateMachineArn;
21+
}
22+
23+
if (activityArn) {
24+
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_STEPFUNCTIONS_ACTIVITY_ARN] = activityArn;
25+
}
26+
27+
const isIncoming = false;
28+
29+
return {
30+
isIncoming,
31+
spanAttributes,
32+
spanKind,
33+
spanName,
34+
};
35+
}
36+
}

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import {
2525
} from './aws/services/bedrock';
2626
import { KinesisServiceExtension } from './aws/services/kinesis';
2727
import { S3ServiceExtension } from './aws/services/s3';
28-
import { AwsLambdaInstrumentationPatch } from './extended-instrumentations/aws-lambda';
28+
import { SecretsManagerServiceExtension } from './aws/services/secretsmanager';
29+
import { StepFunctionsServiceExtension } from './aws/services/step-functions';
2930
import { InstrumentationConfigMap } from '@opentelemetry/auto-instrumentations-node';
3031
import { AwsSdkInstrumentationExtended } from './extended-instrumentations/aws-sdk-instrumentation-extended';
32+
import { AwsLambdaInstrumentationPatch } from './extended-instrumentations/aws-lambda';
3133

3234
export const traceContextEnvironmentKey = '_X_AMZN_TRACE_ID';
3335
const awsPropagator = new AWSXRayPropagator();
@@ -68,11 +70,15 @@ export function applyInstrumentationPatches(
6870
if (services) {
6971
services.set('S3', new S3ServiceExtension());
7072
services.set('Kinesis', new KinesisServiceExtension());
73+
services.set('SecretsManager', new SecretsManagerServiceExtension());
74+
services.set('SFN', new StepFunctionsServiceExtension());
7175
services.set('Bedrock', new BedrockServiceExtension());
7276
services.set('BedrockAgent', new BedrockAgentServiceExtension());
7377
services.set('BedrockAgentRuntime', new BedrockAgentRuntimeServiceExtension());
7478
services.set('BedrockRuntime', new BedrockRuntimeServiceExtension());
7579
patchSqsServiceExtension(services.get('SQS'));
80+
patchSnsServiceExtension(services.get('SNS'));
81+
patchLambdaServiceExtension(services.get('Lambda'));
7682
}
7783
} else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') {
7884
diag.debug('Overriding aws lambda instrumentation');
@@ -154,3 +160,65 @@ function patchSqsServiceExtension(sqsServiceExtension: any): void {
154160
sqsServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook;
155161
}
156162
}
163+
164+
/*
165+
* This patch extends the existing upstream extension for SNS. Extensions allow for custom logic for adding
166+
* service-specific information to spans, such as attributes. Specifically, we are adding logic to add
167+
* `aws.sns.topic.arn` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation.
168+
*
169+
*
170+
* @param snsServiceExtension SNS Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation
171+
*/
172+
function patchSnsServiceExtension(snsServiceExtension: any): void {
173+
if (snsServiceExtension) {
174+
const requestPreSpanHook = snsServiceExtension.requestPreSpanHook;
175+
snsServiceExtension._requestPreSpanHook = requestPreSpanHook;
176+
177+
const patchedRequestPreSpanHook = (
178+
request: NormalizedRequest,
179+
_config: AwsSdkInstrumentationConfig
180+
): RequestMetadata => {
181+
const requestMetadata: RequestMetadata = snsServiceExtension._requestPreSpanHook(request, _config);
182+
if (requestMetadata.spanAttributes) {
183+
const topicArn = request.commandInput?.TopicArn;
184+
if (topicArn) {
185+
requestMetadata.spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_SNS_TOPIC_ARN] = topicArn;
186+
}
187+
}
188+
return requestMetadata;
189+
};
190+
191+
snsServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook;
192+
}
193+
}
194+
195+
/*
196+
* This patch extends the existing upstream extension for Lambda. Extensions allow for custom logic for adding
197+
* service-specific information to spans, such as attributes. Specifically, we are adding logic to add
198+
* `aws.lambda.resource_mapping.id` attribute, to be used to generate RemoteTarget and achieve parity with the Java/Python instrumentation.
199+
*
200+
*
201+
* @param lambdaServiceExtension Lambda Service Extension obtained the service extension list from the AWS SDK OTel Instrumentation
202+
*/
203+
function patchLambdaServiceExtension(lambdaServiceExtension: any): void {
204+
if (lambdaServiceExtension) {
205+
const requestPreSpanHook = lambdaServiceExtension.requestPreSpanHook;
206+
lambdaServiceExtension._requestPreSpanHook = requestPreSpanHook;
207+
208+
const patchedRequestPreSpanHook = (
209+
request: NormalizedRequest,
210+
_config: AwsSdkInstrumentationConfig
211+
): RequestMetadata => {
212+
const requestMetadata: RequestMetadata = lambdaServiceExtension._requestPreSpanHook(request, _config);
213+
if (requestMetadata.spanAttributes) {
214+
const resourceMappingId = request.commandInput?.UUID;
215+
if (resourceMappingId) {
216+
requestMetadata.spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_LAMBDA_RESOURCE_MAPPING_ID] = resourceMappingId;
217+
}
218+
}
219+
return requestMetadata;
220+
};
221+
222+
lambdaServiceExtension.requestPreSpanHook = patchedRequestPreSpanHook;
223+
}
224+
}

0 commit comments

Comments
 (0)