From 7d8d21ad0ec2923b1539e03d58c33e7eb3ca660f Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 18 May 2026 15:02:17 +0200 Subject: [PATCH 1/6] ref(aws-serverless): Vendor aws-sdk instrumentation Closes https://github.com/getsentry/sentry-javascript/issues/20514 Co-Authored-By: Claude Opus 4.6 (1M context) --- .oxlintrc.base.json | 3 +- packages/aws-serverless/package.json | 1 - .../src/integration/{aws.ts => aws/index.ts} | 2 +- .../src/integration/aws/vendored/aws-sdk.ts | 428 ++++++++++++ .../integration/aws/vendored/aws-sdk.types.ts | 124 ++++ .../src/integration/aws/vendored/enums.ts | 37 + .../src/integration/aws/vendored/propwrap.ts | 147 ++++ .../aws/vendored/semconv-obsolete.ts | 64 ++ .../src/integration/aws/vendored/semconv.ts | 606 +++++++++++++++++ .../vendored/services/MessageAttributes.ts | 100 +++ .../aws/vendored/services/ServiceExtension.ts | 60 ++ .../vendored/services/ServicesExtensions.ts | 92 +++ .../aws/vendored/services/bedrock-runtime.ts | 636 ++++++++++++++++++ .../aws/vendored/services/dynamodb.ts | 255 +++++++ .../aws/vendored/services/index.ts | 22 + .../aws/vendored/services/kinesis.ts | 45 ++ .../aws/vendored/services/lambda.ts | 118 ++++ .../integration/aws/vendored/services/s3.ts | 45 ++ .../aws/vendored/services/secretsmanager.ts | 51 ++ .../integration/aws/vendored/services/sns.ts | 96 +++ .../integration/aws/vendored/services/sqs.ts | 160 +++++ .../aws/vendored/services/stepfunctions.ts | 48 ++ .../src/integration/aws/vendored/types.ts | 112 +++ .../src/integration/aws/vendored/utils.ts | 76 +++ yarn.lock | 9 - 25 files changed, 3325 insertions(+), 12 deletions(-) rename packages/aws-serverless/src/integration/{aws.ts => aws/index.ts} (88%) create mode 100644 packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/aws-sdk.types.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/enums.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/propwrap.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/semconv-obsolete.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/semconv.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/MessageAttributes.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/ServiceExtension.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/ServicesExtensions.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/dynamodb.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/index.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/kinesis.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/lambda.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/s3.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/secretsmanager.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/sns.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/sqs.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/services/stepfunctions.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/types.ts create mode 100644 packages/aws-serverless/src/integration/aws/vendored/utils.ts diff --git a/.oxlintrc.base.json b/.oxlintrc.base.json index 7a6834cec9f7..3725c2a7f421 100644 --- a/.oxlintrc.base.json +++ b/.oxlintrc.base.json @@ -146,7 +146,8 @@ "**/integrations/tracing/genericPool/vendored/**/*.ts", "**/integrations/fs/vendored/**/*.ts", "**/integrations/tracing/knex/vendored/**/*.ts", - "**/integrations/tracing/mongo/vendored/**/*.ts" + "**/integrations/tracing/mongo/vendored/**/*.ts", + "**/integration/aws/vendored/**/*.ts" ], "rules": { "typescript/no-explicit-any": "off" diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 71a8e4ac6a0f..2f4ed3fcf921 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -68,7 +68,6 @@ "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/instrumentation": "^0.214.0", - "@opentelemetry/instrumentation-aws-sdk": "0.69.0", "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry/core": "10.53.1", "@sentry/node": "10.53.1", diff --git a/packages/aws-serverless/src/integration/aws.ts b/packages/aws-serverless/src/integration/aws/index.ts similarity index 88% rename from packages/aws-serverless/src/integration/aws.ts rename to packages/aws-serverless/src/integration/aws/index.ts index d67f9e529f21..b45a163491d9 100644 --- a/packages/aws-serverless/src/integration/aws.ts +++ b/packages/aws-serverless/src/integration/aws/index.ts @@ -1,5 +1,5 @@ import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { AwsInstrumentation } from '@opentelemetry/instrumentation-aws-sdk'; +import { AwsInstrumentation } from './vendored/aws-sdk'; import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; /** diff --git a/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts b/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts new file mode 100644 index 000000000000..0fb87e79186d --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.ts @@ -0,0 +1,428 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Span, SpanKind, context, trace, diag, SpanStatusCode } from '@opentelemetry/api'; +import { hrTime, suppressTracing } from '@opentelemetry/core'; +import { AttributeNames } from './enums'; +import { ServicesExtensions } from './services'; +import { + AwsSdkInstrumentationConfig, + AwsSdkRequestHookInformation, + AwsSdkResponseHookInformation, + NormalizedRequest, + NormalizedResponse, +} from './types'; +import { + InstrumentationBase, + InstrumentationModuleDefinition, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, + isWrapped, + safeExecuteInTheMiddle, + SemconvStability, + semconvStabilityFromStr, +} from '@opentelemetry/instrumentation'; +import type { + MiddlewareStack, + HandlerExecutionContext, + Command as AwsV3Command, + Handler as AwsV3MiddlewareHandler, + InitializeHandlerArguments, +} from '@aws-sdk/types'; +import { + bindPromise, + extractAttributesFromNormalizedRequest, + normalizeV3Request, + removeSuffixFromStringIfExists, +} from './utils'; +import { propwrap } from './propwrap'; +import { RequestMetadata } from './services/ServiceExtension'; +import { ATTR_HTTP_STATUS_CODE } from './semconv'; +import { ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions'; +import { SDK_VERSION } from '@sentry/core'; + +const PACKAGE_NAME = '@sentry/instrumentation-aws-sdk'; + +const V3_CLIENT_CONFIG_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.client.config'); +type V3PluginCommand = AwsV3Command & { + [V3_CLIENT_CONFIG_KEY]?: any; +}; + +export class AwsInstrumentation extends InstrumentationBase { + static readonly component = 'aws-sdk'; + // need declare since initialized in callbacks from super constructor + declare private servicesExtensions: ServicesExtensions; + + private _httpSemconvStability: SemconvStability; + private _dbSemconvStability: SemconvStability; + + constructor(config: AwsSdkInstrumentationConfig = {}) { + super(PACKAGE_NAME, SDK_VERSION, config); + this._httpSemconvStability = semconvStabilityFromStr('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN); + this._dbSemconvStability = semconvStabilityFromStr('database', process.env.OTEL_SEMCONV_STABILITY_OPT_IN); + } + + protected init(): InstrumentationModuleDefinition[] { + const v3MiddlewareStackFileOldVersions = new InstrumentationNodeModuleFile( + '@aws-sdk/middleware-stack/dist/cjs/MiddlewareStack.js', + ['>=3.1.0 <3.35.0'], + this.patchV3ConstructStack.bind(this), + this.unpatchV3ConstructStack.bind(this), + ); + const v3MiddlewareStackFileNewVersions = new InstrumentationNodeModuleFile( + '@aws-sdk/middleware-stack/dist-cjs/MiddlewareStack.js', + ['>=3.35.0'], + this.patchV3ConstructStack.bind(this), + this.unpatchV3ConstructStack.bind(this), + ); + + // as for aws-sdk v3.13.1, constructStack is exported from @aws-sdk/middleware-stack as + // getter instead of function, which fails shimmer. + // so we are patching the MiddlewareStack.js file directly to get around it. + const v3MiddlewareStack = new InstrumentationNodeModuleDefinition( + '@aws-sdk/middleware-stack', + ['^3.1.0'], + undefined, + undefined, + [v3MiddlewareStackFileOldVersions, v3MiddlewareStackFileNewVersions], + ); + + // Patch for @smithy/middleware-stack for @aws-sdk/* packages v3.363.0+. + // As of @smithy/middleware-stack@2.1.0 `constructStack` is only available + // as a getter, so we cannot use `this._wrap()`. + const self = this; + const v3SmithyMiddlewareStack = new InstrumentationNodeModuleDefinition( + '@smithy/middleware-stack', + ['>=2.0.0'], + (moduleExports, moduleVersion) => { + const newExports = propwrap(moduleExports, 'constructStack', (orig: any) => { + self._diag.debug('propwrapping aws-sdk v3 constructStack'); + return self._getV3ConstructStackPatch(moduleVersion, orig); + }); + return newExports; + }, + ); + + const v3SmithyClient = new InstrumentationNodeModuleDefinition( + '@aws-sdk/smithy-client', + ['^3.1.0'], + this.patchV3SmithyClient.bind(this), + this.unpatchV3SmithyClient.bind(this), + ); + + // patch for new @smithy/smithy-client for aws-sdk packages v3.363.0+ + const v3NewSmithyClient = new InstrumentationNodeModuleDefinition( + '@smithy/smithy-client', + ['>=1.0.3'], + this.patchV3SmithyClient.bind(this), + this.unpatchV3SmithyClient.bind(this), + ); + + return [v3MiddlewareStack, v3SmithyMiddlewareStack, v3SmithyClient, v3NewSmithyClient]; + } + + protected patchV3ConstructStack(moduleExports: any, moduleVersion?: string) { + this._wrap(moduleExports, 'constructStack', this._getV3ConstructStackPatch.bind(this, moduleVersion)); + return moduleExports; + } + + protected unpatchV3ConstructStack(moduleExports: any) { + this._unwrap(moduleExports, 'constructStack'); + return moduleExports; + } + + protected patchV3SmithyClient(moduleExports: any) { + this._wrap(moduleExports.Client.prototype, 'send', this._getV3SmithyClientSendPatch.bind(this)); + return moduleExports; + } + + protected unpatchV3SmithyClient(moduleExports: any) { + this._unwrap(moduleExports.Client.prototype, 'send'); + return moduleExports; + } + + private _startAwsV3Span(normalizedRequest: NormalizedRequest, metadata: RequestMetadata): Span { + const name = metadata.spanName ?? `${normalizedRequest.serviceName}.${normalizedRequest.commandName}`; + const newSpan = this.tracer.startSpan(name, { + kind: metadata.spanKind ?? SpanKind.CLIENT, + attributes: { + ...extractAttributesFromNormalizedRequest(normalizedRequest), + ...metadata.spanAttributes, + }, + }); + + return newSpan; + } + + private _callUserPreRequestHook(span: Span, request: NormalizedRequest, moduleVersion: string | undefined) { + const { preRequestHook } = this.getConfig(); + if (preRequestHook) { + const requestInfo: AwsSdkRequestHookInformation = { + moduleVersion, + request, + }; + safeExecuteInTheMiddle( + () => preRequestHook(span, requestInfo), + (e: Error | undefined) => { + if (e) diag.error(`${AwsInstrumentation.component} instrumentation: preRequestHook error`, e); + }, + true, + ); + } + } + + private _callUserResponseHook(span: Span, response: NormalizedResponse) { + const { responseHook } = this.getConfig(); + if (!responseHook) return; + + const responseInfo: AwsSdkResponseHookInformation = { + response, + }; + safeExecuteInTheMiddle( + () => responseHook(span, responseInfo), + (e: Error | undefined) => { + if (e) diag.error(`${AwsInstrumentation.component} instrumentation: responseHook error`, e); + }, + true, + ); + } + + private _callUserExceptionResponseHook(span: Span, request: NormalizedRequest, err: any) { + const { exceptionHook } = this.getConfig(); + if (!exceptionHook) return; + const requestInfo: AwsSdkRequestHookInformation = { + request, + }; + + safeExecuteInTheMiddle( + () => exceptionHook(span, requestInfo, err), + (e: Error | undefined) => { + if (e) diag.error(`${AwsInstrumentation.component} instrumentation: exceptionHook error`, e); + }, + true, + ); + } + + private _getV3ConstructStackPatch( + moduleVersion: string | undefined, + original: (...args: unknown[]) => MiddlewareStack, + ) { + const self = this; + return function constructStack(this: any, ...args: unknown[]): MiddlewareStack { + const stack: MiddlewareStack = original.apply(this, args); + self.patchV3MiddlewareStack(moduleVersion, stack); + return stack; + }; + } + + private _getV3SmithyClientSendPatch(original: (...args: unknown[]) => Promise) { + return function send(this: any, command: V3PluginCommand, ...args: unknown[]): Promise { + command[V3_CLIENT_CONFIG_KEY] = this.config; + return original.apply(this, [command, ...args]); + }; + } + + private patchV3MiddlewareStack(moduleVersion: string | undefined, middlewareStackToPatch: MiddlewareStack) { + if (!isWrapped(middlewareStackToPatch.resolve)) { + this._wrap(middlewareStackToPatch, 'resolve', this._getV3MiddlewareStackResolvePatch.bind(this, moduleVersion)); + } + + // 'clone' and 'concat' functions are internally calling 'constructStack' which is in same + // module, thus not patched, and we need to take care of it specifically. + this._wrap(middlewareStackToPatch, 'clone', this._getV3MiddlewareStackClonePatch.bind(this, moduleVersion)); + this._wrap(middlewareStackToPatch, 'concat', this._getV3MiddlewareStackClonePatch.bind(this, moduleVersion)); + } + + private _getV3MiddlewareStackClonePatch( + moduleVersion: string | undefined, + original: (...args: any[]) => MiddlewareStack, + ) { + const self = this; + return function (this: any, ...args: any[]) { + const newStack = original.apply(this, args); + self.patchV3MiddlewareStack(moduleVersion, newStack); + return newStack; + }; + } + + private _getV3MiddlewareStackResolvePatch( + moduleVersion: string | undefined, + original: (_handler: any, context: HandlerExecutionContext) => AwsV3MiddlewareHandler, + ) { + const self = this; + return function ( + this: any, + _handler: any, + awsExecutionContext: HandlerExecutionContext, + ): AwsV3MiddlewareHandler { + const origHandler = original.call(this, _handler, awsExecutionContext); + const patchedHandler = function ( + this: any, + command: InitializeHandlerArguments & { + [V3_CLIENT_CONFIG_KEY]?: any; + }, + ): Promise { + const clientConfig = command[V3_CLIENT_CONFIG_KEY]; + const regionPromise = clientConfig?.region?.(); + const serviceName = + clientConfig?.serviceId ?? + removeSuffixFromStringIfExists( + // Use 'AWS' as a fallback serviceName to match type definition. + // In practice, `clientName` should always be set. + awsExecutionContext.clientName || 'AWS', + 'Client', + ); + const commandName = awsExecutionContext.commandName ?? command.constructor?.name; + const normalizedRequest = normalizeV3Request(serviceName, commandName, command.input, undefined); + const requestMetadata = self.servicesExtensions.requestPreSpanHook( + normalizedRequest, + self.getConfig(), + self._diag, + self._dbSemconvStability, + ); + const startTime = hrTime(); + const span = self._startAwsV3Span(normalizedRequest, requestMetadata); + const activeContextWithSpan = trace.setSpan(context.active(), span); + + const handlerPromise = new Promise((resolve, reject) => { + Promise.resolve(regionPromise) + .then(resolvedRegion => { + normalizedRequest.region = resolvedRegion; + span.setAttribute(AttributeNames.CLOUD_REGION, resolvedRegion); + }) + .catch(e => { + // there is nothing much we can do in this case. + // we'll just continue without region + diag.debug( + `${AwsInstrumentation.component} instrumentation: failed to extract region from async function`, + e, + ); + }) + .finally(() => { + self._callUserPreRequestHook(span, normalizedRequest, moduleVersion); + const resultPromise = context.with(activeContextWithSpan, () => { + self.servicesExtensions.requestPostSpanHook(normalizedRequest); + return self._callOriginalFunction(() => origHandler.call(this, command)); + }); + const promiseWithResponseLogic = resultPromise + .then((response: any) => { + const requestId = response.output?.$metadata?.requestId; + if (requestId) { + span.setAttribute(AttributeNames.AWS_REQUEST_ID, requestId); + } + + const httpStatusCode = response.output?.$metadata?.httpStatusCode; + if (httpStatusCode) { + if (self._httpSemconvStability & SemconvStability.OLD) { + span.setAttribute(ATTR_HTTP_STATUS_CODE, httpStatusCode); + } + if (self._httpSemconvStability & SemconvStability.STABLE) { + span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatusCode); + } + } + + const extendedRequestId = response.output?.$metadata?.extendedRequestId; + if (extendedRequestId) { + span.setAttribute(AttributeNames.AWS_REQUEST_EXTENDED_ID, extendedRequestId); + } + + const normalizedResponse: NormalizedResponse = { + data: response.output, + request: normalizedRequest, + requestId: requestId, + }; + const override = self.servicesExtensions.responseHook( + normalizedResponse, + span, + self.tracer, + self.getConfig(), + startTime, + ); + if (override) { + response.output = override; + normalizedResponse.data = override; + } + self._callUserResponseHook(span, normalizedResponse); + return response; + }) + .catch((err: any) => { + const requestId = err?.RequestId; + if (requestId) { + span.setAttribute(AttributeNames.AWS_REQUEST_ID, requestId); + } + + const httpStatusCode = err?.$metadata?.httpStatusCode; + if (httpStatusCode) { + if (self._httpSemconvStability & SemconvStability.OLD) { + span.setAttribute(ATTR_HTTP_STATUS_CODE, httpStatusCode); + } + if (self._httpSemconvStability & SemconvStability.STABLE) { + span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatusCode); + } + } + + const extendedRequestId = err?.extendedRequestId; + if (extendedRequestId) { + span.setAttribute(AttributeNames.AWS_REQUEST_EXTENDED_ID, extendedRequestId); + } + + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err.message, + }); + span.recordException(err); + self._callUserExceptionResponseHook(span, normalizedRequest, err); + throw err; + }) + .finally(() => { + if (!requestMetadata.isStream) { + span.end(); + } + }); + promiseWithResponseLogic + .then((res: any) => { + resolve(res); + }) + .catch((err: any) => reject(err)); + }); + }); + + return requestMetadata.isIncoming ? bindPromise(handlerPromise, activeContextWithSpan, 2) : handlerPromise; + }; + return patchedHandler; + }; + } + + private _callOriginalFunction(originalFunction: (...args: any[]) => T): T { + if (this.getConfig().suppressInternalInstrumentation) { + return context.with(suppressTracing(context.active()), originalFunction); + } else { + return originalFunction(); + } + } + + override _updateMetricInstruments() { + if (!this.servicesExtensions) { + this.servicesExtensions = new ServicesExtensions(); + } + this.servicesExtensions.updateMetricInstruments(this.meter); + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.types.ts b/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.types.ts new file mode 100644 index 000000000000..511eee99c7da --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/aws-sdk.types.ts @@ -0,0 +1,124 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +/* + * AWS SDK for JavaScript + * Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * This product includes software developed at + * Amazon Web Services, Inc. (http://aws.amazon.com/). + */ + +/* + These are slightly modified and simplified versions of the actual SQS types included + in the official distribution: + https://github.com/aws/aws-sdk-js/blob/master/clients/sqs.d.ts + These are brought here to avoid having users install the `aws-sdk` whenever they + require this instrumentation. +*/ + +interface Blob {} +type Binary = Buffer | Uint8Array | Blob | string; + +// eslint-disable-next-line @typescript-eslint/no-namespace -- Prefer to contain the types copied over in one location +export namespace SNS { + interface MessageAttributeValue { + /** + * Amazon SNS supports the following logical data types: String, String.Array, Number, and Binary. For more information, see Message Attribute Data Types. + */ + DataType: string; + /** + * Strings are Unicode with UTF8 binary encoding. For a list of code values, see ASCII Printable Characters. + */ + StringValue?: string; + /** + * Binary type attributes can store any binary data, for example, compressed data, encrypted data, or images. + */ + BinaryValue?: Binary; + } + + export type MessageAttributeMap = { [key: string]: MessageAttributeValue }; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace -- Prefer to contain the types copied over in one location +export namespace SQS { + type StringList = string[]; + type BinaryList = Binary[]; + interface MessageAttributeValue { + /** + * Strings are Unicode with UTF-8 binary encoding. For a list of code values, see ASCII Printable Characters. + */ + StringValue?: string; + /** + * Binary type attributes can store any binary data, such as compressed data, encrypted data, or images. + */ + BinaryValue?: Binary; + /** + * Not implemented. Reserved for future use. + */ + StringListValues?: StringList; + /** + * Not implemented. Reserved for future use. + */ + BinaryListValues?: BinaryList; + /** + * Amazon SQS supports the following logical data types: String, Number, and Binary. For the Number data type, you must use StringValue. You can also append custom labels. For more information, see Amazon SQS Message Attributes in the Amazon SQS Developer Guide. + */ + DataType: string; + } + + export type MessageBodyAttributeMap = { + [key: string]: MessageAttributeValue; + }; + + type MessageSystemAttributeMap = { [key: string]: string }; + + export interface Message { + /** + * A unique identifier for the message. A MessageId is considered unique across all accounts for an extended period of time. + */ + MessageId?: string; + /** + * An identifier associated with the act of receiving the message. A new receipt handle is returned every time you receive a message. When deleting a message, you provide the last received receipt handle to delete the message. + */ + ReceiptHandle?: string; + /** + * An MD5 digest of the non-URL-encoded message body string. + */ + MD5OfBody?: string; + /** + * The message's contents (not URL-encoded). + */ + Body?: string; + /** + * A map of the attributes requested in ReceiveMessage to their respective values. Supported attributes: ApproximateReceiveCount ApproximateFirstReceiveTimestamp MessageDeduplicationId MessageGroupId SenderId SentTimestamp SequenceNumber ApproximateFirstReceiveTimestamp and SentTimestamp are each returned as an integer representing the epoch time in milliseconds. + */ + Attributes?: MessageSystemAttributeMap; + /** + * An MD5 digest of the non-URL-encoded message attribute string. You can use this attribute to verify that Amazon SQS received the message correctly. Amazon SQS URL-decodes the message before creating the MD5 digest. For information about MD5, see RFC1321. + */ + MD5OfMessageAttributes?: string; + /** + * Each message attribute consists of a Name, Type, and Value. For more information, see Amazon SQS message attributes in the Amazon SQS Developer Guide. + */ + MessageAttributes?: MessageBodyAttributeMap; + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/enums.ts b/packages/aws-serverless/src/integration/aws/vendored/enums.ts new file mode 100644 index 000000000000..970c6af9c953 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/enums.ts @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +export enum AttributeNames { + AWS_OPERATION = 'aws.operation', + CLOUD_REGION = 'cloud.region', + AWS_SERVICE_API = 'aws.service.api', + AWS_SERVICE_NAME = 'aws.service.name', + AWS_SERVICE_IDENTIFIER = 'aws.service.identifier', + AWS_REQUEST_ID = 'aws.request.id', + AWS_REQUEST_EXTENDED_ID = 'aws.request.extended_id', + AWS_SIGNATURE_VERSION = 'aws.signature.version', + + // TODO: Add these semantic attributes to: + // - https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-semantic-conventions/src/trace/SemanticAttributes.ts + // For S3, see specification: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/object-stores/s3.md + AWS_S3_BUCKET = 'aws.s3.bucket', + AWS_KINESIS_STREAM_NAME = 'aws.kinesis.stream.name', +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/propwrap.ts b/packages/aws-serverless/src/integration/aws/vendored/propwrap.ts new file mode 100644 index 000000000000..2250eef7c713 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/propwrap.ts @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +/* + * This block is derived from esbuild's bundling support. + * https://github.com/evanw/esbuild/blob/v0.14.42/internal/runtime/runtime.go#L22 + * + * License: + * MIT License + * + * Copyright (c) 2020 Evan Wallace + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +const __defProp = Object.defineProperty; +const __getOwnPropDesc = Object.getOwnPropertyDescriptor; +const __hasOwnProp = Object.prototype.hasOwnProperty; +const __getOwnPropNames = Object.getOwnPropertyNames; +const __copyProps = (to: any, from: any, except: string, desc?: PropertyDescriptor | undefined) => { + if ((from && typeof from === 'object') || typeof from === 'function') { + for (const key of __getOwnPropNames(from)) { + if (!__hasOwnProp.call(to, key) && key !== except) { + __defProp(to, key, { + get: () => from[key] as any, + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, + }); + } + } + } + return to; +}; + +/** + * Return a new object that is a copy of `obj`, with its `subpath` property + * replaced with the return value of `wrapper(original)`. + * + * This is similar to shimmer (i.e. `InstrumentationBase.prototype._wrap`). + * However, it uses a different technique to support wrapping properties that + * are only available via a getter (i.e. their property descriptor is `.writable + * === false`). + * + * For example: + * var os = propwrap(require('os'), 'platform', (orig) => { + * return function wrappedPlatform () { + * return orig().toUpperCase() + * } + * }) + * console.log(os.platform()) // => DARWIN + * + * The subpath can indicate a nested property. Each property in that subpath, + * except the last, must identify an *Object*. + * + * Limitations: + * - This doesn't handle possible Symbol properties on the copied object(s). + * - This cannot wrap a property of a function, because we cannot create a + * copy of the function. + * + * @param {object} obj + * @param {string} subpath - The property subpath on `obj` to wrap. This may + * point to a nested property by using a '.' to separate levels. For example: + * var fs = wrap(fs, 'promises.sync', (orig) => { ... }) + * @param {Function} wrapper - A function of the form `function (orig)`, where + * `orig` is the original property value. This must synchronously return the + * new property value. + * @returns {object} A new object with the wrapped property. + * @throws {TypeError} if the subpath points to a non-existent property, or if + * any but the last subpath part points to a non-Object. + */ +export const propwrap = (obj: any, subpath: string, wrapper: Function): any => { + const parts = subpath.split('.'); + const namespaces = [obj]; + let namespace = obj; + let key: string | undefined; + let val: any; + + // 1. Traverse the subpath parts to sanity check and get references to the + // Objects that we will be copying. + for (let i = 0; i < parts.length; i++) { + key = parts[i]; + val = namespace[key!]; + if (!val) { + throw new TypeError(`cannot wrap "${subpath}": ".${parts.slice(0, i).join('.')}" is ${typeof val}`); + } else if (i < parts.length - 1) { + if (typeof val !== 'object') { + throw new TypeError(`cannot wrap "${subpath}": ".${parts.slice(0, i).join('.')}" is not an Object`); + } + namespace = val; + namespaces.push(namespace); + } + } + + // 2. Now work backwards, wrapping each namespace with a new object that has a + // copy of all the properties, except the one that we've wrapped. + for (let i = parts.length - 1; i >= 0; i--) { + key = parts[i]; + namespace = namespaces[i]; + if (i === parts.length - 1) { + const orig = namespace[key!]; + val = wrapper(orig); + } else { + val = namespaces[i + 1]; + } + const desc = __getOwnPropDesc(namespace, key!); + const wrappedNamespace = __defProp({}, key!, { + value: val, + enumerable: !desc || desc.enumerable, + }); + __copyProps(wrappedNamespace, namespace, key!); + namespaces[i] = wrappedNamespace; + } + + return namespaces[0]; +}; diff --git a/packages/aws-serverless/src/integration/aws/vendored/semconv-obsolete.ts b/packages/aws-serverless/src/integration/aws/vendored/semconv-obsolete.ts new file mode 100644 index 000000000000..ea2a841c8a56 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/semconv-obsolete.ts @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +/* + * This file contains constants for values that where replaced/removed from + * Semantic Conventions long enough ago that they do not have `ATTR_*` + * constants in the `@opentelemetry/semantic-conventions` package. Eventually + * it is expected that this instrumention will be updated to emit telemetry + * using modern Semantic Conventions, dropping the need for the constants in + * this file. + */ + +/** + * The execution ID of the current function execution. + * + * @deprecated Use ATTR_FAAS_INVOCATION_ID in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}). + */ +export const ATTR_FAAS_EXECUTION = 'faas.execution' as const; + +/** + * The message destination name. This might be equal to the span name but is required nevertheless. + * + * @deprecated Use ATTR_MESSAGING_DESTINATION_NAME in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}). + */ +export const ATTR_MESSAGING_DESTINATION = 'messaging.destination' as const; + +/** + * The kind of message destination. + * + * @deprecated Removed in semconv v1.20.0. + */ +export const ATTR_MESSAGING_DESTINATION_KIND = 'messaging.destination_kind' as const; + +/** + * The kind of message destination. + * + * @deprecated Removed in semconv v1.20.0. + */ +export const MESSAGING_DESTINATION_KIND_VALUE_TOPIC = 'topic' as const; + +/** + * A string identifying the kind of message consumption as defined in the [Operation names](#operation-names) section above. If the operation is "send", this attribute MUST NOT be set, since the operation can be inferred from the span kind in that case. + * + * @deprecated Use MESSAGING_OPERATION_TYPE_VALUE_RECEIVE in [incubating entry-point]({@link https://github.com/open-telemetry/opentelemetry-js/blob/main/semantic-conventions/README.md#unstable-semconv}). + */ +export const MESSAGING_OPERATION_VALUE_RECEIVE = 'receive' as const; diff --git a/packages/aws-serverless/src/integration/aws/vendored/semconv.ts b/packages/aws-serverless/src/integration/aws/vendored/semconv.ts new file mode 100644 index 000000000000..f813cd0b4987 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/semconv.ts @@ -0,0 +1,606 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +/* + * This file contains a copy of unstable semantic convention definitions + * used by this package. + * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv + */ + +/** + * The JSON-serialized value of each item in the `AttributeDefinitions` request field. + * + * @example ["{ "AttributeName": "string", "AttributeType": "string" }"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS = 'aws.dynamodb.attribute_definitions' as const; + +/** + * The value of the `ConsistentRead` request parameter. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_CONSISTENT_READ = 'aws.dynamodb.consistent_read' as const; + +/** + * The JSON-serialized value of each item in the `ConsumedCapacity` response field. + * + * @example ["{ "CapacityUnits": number, "GlobalSecondaryIndexes": { "string" : { "CapacityUnits": number, "ReadCapacityUnits": number, "WriteCapacityUnits": number } }, "LocalSecondaryIndexes": { "string" : { "CapacityUnits": number, "ReadCapacityUnits": number, "WriteCapacityUnits": number } }, "ReadCapacityUnits": number, "Table": { "CapacityUnits": number, "ReadCapacityUnits": number, "WriteCapacityUnits": number }, "TableName": "string", "WriteCapacityUnits": number }"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_CONSUMED_CAPACITY = 'aws.dynamodb.consumed_capacity' as const; + +/** + * The value of the `Count` response parameter. + * + * @example 10 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_COUNT = 'aws.dynamodb.count' as const; + +/** + * The value of the `ExclusiveStartTableName` request parameter. + * + * @example Users + * @example CatsTable + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_EXCLUSIVE_START_TABLE = 'aws.dynamodb.exclusive_start_table' as const; + +/** + * The JSON-serialized value of each item of the `GlobalSecondaryIndexes` request field + * + * @example ["{ "IndexName": "string", "KeySchema": [ { "AttributeName": "string", "KeyType": "string" } ], "Projection": { "NonKeyAttributes": [ "string" ], "ProjectionType": "string" }, "ProvisionedThroughput": { "ReadCapacityUnits": number, "WriteCapacityUnits": number } }"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES = 'aws.dynamodb.global_secondary_indexes' as const; + +/** + * The JSON-serialized value of each item in the `GlobalSecondaryIndexUpdates` request field. + * + * @example ["{ "Create": { "IndexName": "string", "KeySchema": [ { "AttributeName": "string", "KeyType": "string" } ], "Projection": { "NonKeyAttributes": [ "string" ], "ProjectionType": "string" }, "ProvisionedThroughput": { "ReadCapacityUnits": number, "WriteCapacityUnits": number } }"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES = 'aws.dynamodb.global_secondary_index_updates' as const; + +/** + * The value of the `IndexName` request parameter. + * + * @example name_to_group + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_INDEX_NAME = 'aws.dynamodb.index_name' as const; + +/** + * The JSON-serialized value of the `ItemCollectionMetrics` response field. + * + * @example { "string" : [ { "ItemCollectionKey": { "string" : { "B": blob, "BOOL": boolean, "BS": [ blob ], "L": [ "AttributeValue" ], "M": { "string" : "AttributeValue" }, "N": "string", "NS": [ "string" ], "NULL": boolean, "S": "string", "SS": [ "string" ] } }, "SizeEstimateRangeGB": [ number ] } ] } + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_ITEM_COLLECTION_METRICS = 'aws.dynamodb.item_collection_metrics' as const; + +/** + * The value of the `Limit` request parameter. + * + * @example 10 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_LIMIT = 'aws.dynamodb.limit' as const; + +/** + * The JSON-serialized value of each item of the `LocalSecondaryIndexes` request field. + * + * @example ["{ "IndexArn": "string", "IndexName": "string", "IndexSizeBytes": number, "ItemCount": number, "KeySchema": [ { "AttributeName": "string", "KeyType": "string" } ], "Projection": { "NonKeyAttributes": [ "string" ], "ProjectionType": "string" } }"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES = 'aws.dynamodb.local_secondary_indexes' as const; + +/** + * The value of the `ProjectionExpression` request parameter. + * + * @example Title + * @example Title, Price, Color + * @example Title, Description, RelatedItems, ProductReviews + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_PROJECTION = 'aws.dynamodb.projection' as const; + +/** + * The value of the `ProvisionedThroughput.ReadCapacityUnits` request parameter. + * + * @example 1.0 + * @example 2.0 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY = 'aws.dynamodb.provisioned_read_capacity' as const; + +/** + * The value of the `ProvisionedThroughput.WriteCapacityUnits` request parameter. + * + * @example 1.0 + * @example 2.0 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY = 'aws.dynamodb.provisioned_write_capacity' as const; + +/** + * The value of the `ScannedCount` response parameter. + * + * @example 50 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_SCANNED_COUNT = 'aws.dynamodb.scanned_count' as const; + +/** + * The value of the `ScanIndexForward` request parameter. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_SCAN_FORWARD = 'aws.dynamodb.scan_forward' as const; + +/** + * The value of the `Segment` request parameter. + * + * @example 10 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_SEGMENT = 'aws.dynamodb.segment' as const; + +/** + * The value of the `Select` request parameter. + * + * @example ALL_ATTRIBUTES + * @example COUNT + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_SELECT = 'aws.dynamodb.select' as const; + +/** + * The number of items in the `TableNames` response parameter. + * + * @example 20 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_TABLE_COUNT = 'aws.dynamodb.table_count' as const; + +/** + * The keys in the `RequestItems` object field. + * + * @example ["Users", "Cats"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_TABLE_NAMES = 'aws.dynamodb.table_names' as const; + +/** + * The value of the `TotalSegments` request parameter. + * + * @example 100 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_DYNAMODB_TOTAL_SEGMENTS = 'aws.dynamodb.total_segments' as const; + +/** + * The ARN of the Secret stored in the Secrets Mangger + * + * @example arn:aws:secretsmanager:us-east-1:123456789012:secret:SecretName-6RandomCharacters + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_SECRETSMANAGER_SECRET_ARN = 'aws.secretsmanager.secret.arn' as const; + +/** + * The ARN of the AWS SNS Topic. An Amazon SNS [topic](https://docs.aws.amazon.com/sns/latest/dg/sns-create-topic.html) is a logical access point that acts as a communication channel. + * + * @example arn:aws:sns:us-east-1:123456789012:mystack-mytopic-NZJ5JSMVGFIE + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_SNS_TOPIC_ARN = 'aws.sns.topic.arn' as const; + +/** + * The ARN of the AWS Step Functions Activity. + * + * @example arn:aws:states:us-east-1:123456789012:activity:get-greeting + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_STEP_FUNCTIONS_ACTIVITY_ARN = 'aws.step_functions.activity.arn' as const; + +/** + * The ARN of the AWS Step Functions State Machine. + * + * @example arn:aws:states:us-east-1:123456789012:stateMachine:myStateMachine:1 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN = 'aws.step_functions.state_machine.arn' as const; + +/** + * Deprecated, use `db.namespace` instead. + * + * @example customers + * @example main + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `db.namespace`. + */ +export const ATTR_DB_NAME = 'db.name' as const; + +/** + * Deprecated, use `db.operation.name` instead. + * + * @example findAndModify + * @example HMSET + * @example SELECT + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `db.operation.name`. + */ +export const ATTR_DB_OPERATION = 'db.operation' as const; + +/** + * The database statement being executed. + * + * @example SELECT * FROM wuser_table + * @example SET mykey "WuValue" + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `db.query.text`. + */ +export const ATTR_DB_STATEMENT = 'db.statement' as const; + +/** + * Deprecated, use `db.system.name` instead. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `db.system.name`. + */ +export const ATTR_DB_SYSTEM = 'db.system' as const; + +/** + * The name of the invoked function. + * + * @example "my-function" + * + * @note **SHOULD** be equal to the `faas.name` resource attribute of the invoked function. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_FAAS_INVOKED_NAME = 'faas.invoked_name' as const; + +/** + * The cloud provider of the invoked function. + * + * @note **SHOULD** be equal to the `cloud.provider` resource attribute of the invoked function. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_FAAS_INVOKED_PROVIDER = 'faas.invoked_provider' as const; + +/** + * The cloud region of the invoked function. + * + * @example "eu-central-1" + * + * @note **SHOULD** be equal to the `cloud.region` resource attribute of the invoked function. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_FAAS_INVOKED_REGION = 'faas.invoked_region' as const; + +/** + * The name of the operation being performed. + * + * @note If one of the predefined values applies, but specific system uses a different name it's **RECOMMENDED** to document it in the semantic conventions for specific GenAI system and use system-specific name in the instrumentation. If a different name is not documented, instrumentation libraries **SHOULD** use applicable predefined value. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_OPERATION_NAME = 'gen_ai.operation.name' as const; + +/** + * The maximum number of tokens the model generates for a request. + * + * @example 100 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_REQUEST_MAX_TOKENS = 'gen_ai.request.max_tokens' as const; + +/** + * The name of the GenAI model a request is being made to. + * + * @example "gpt-4" + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_REQUEST_MODEL = 'gen_ai.request.model' as const; + +/** + * List of sequences that the model will use to stop generating further tokens. + * + * @example ["forest", "lived"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_REQUEST_STOP_SEQUENCES = 'gen_ai.request.stop_sequences' as const; + +/** + * The temperature setting for the GenAI request. + * + * @example 0.0 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_REQUEST_TEMPERATURE = 'gen_ai.request.temperature' as const; + +/** + * The top_p sampling setting for the GenAI request. + * + * @example 1.0 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_REQUEST_TOP_P = 'gen_ai.request.top_p' as const; + +/** + * Array of reasons the model stopped generating tokens, corresponding to each generation received. + * + * @example ["stop"] + * @example ["stop", "length"] + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_RESPONSE_FINISH_REASONS = 'gen_ai.response.finish_reasons' as const; + +/** + * Deprecated, use `gen_ai.provider.name` instead. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `gen_ai.provider.name`. + */ +export const ATTR_GEN_AI_SYSTEM = 'gen_ai.system' as const; + +/** + * The type of token being counted. + * + * @example input + * @example output + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_TOKEN_TYPE = 'gen_ai.token.type' as const; + +/** + * The number of tokens used in the GenAI input (prompt). + * + * @example 100 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens' as const; + +/** + * The number of tokens used in the GenAI response (completion). + * + * @example 180 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_GEN_AI_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens' as const; + +/** + * Deprecated, use `http.response.status_code` instead. + * + * @example 200 + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `http.response.status_code`. + */ +export const ATTR_HTTP_STATUS_CODE = 'http.status_code' as const; + +/** + * The number of messages sent, received, or processed in the scope of the batching operation. + * + * @example 0 + * @example 1 + * @example 2 + * + * @note Instrumentations **SHOULD NOT** set `messaging.batch.message_count` on spans that operate with a single message. When a messaging client library supports both batch and single-message API for the same operation, instrumentations **SHOULD** use `messaging.batch.message_count` for batching APIs and **SHOULD NOT** use it for single-message APIs. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_MESSAGING_BATCH_MESSAGE_COUNT = 'messaging.batch.message_count' as const; + +/** + * The message destination name + * + * @example MyQueue + * @example MyTopic + * + * @note Destination name **SHOULD** uniquely identify a specific queue, topic or other entity within the broker. If + * the broker doesn't have such notion, the destination name **SHOULD** uniquely identify the broker. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_MESSAGING_DESTINATION_NAME = 'messaging.destination.name' as const; + +/** + * A value used by the messaging system as an identifier for the message, represented as a string. + * + * @example "452a7c7c7c7048c2f887f61572b18fc2" + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_MESSAGING_MESSAGE_ID = 'messaging.message.id' as const; + +/** + * Deprecated, use `messaging.operation.type` instead. + * + * @example publish + * @example create + * @example process + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `messaging.operation.type`. + */ +export const ATTR_MESSAGING_OPERATION = 'messaging.operation' as const; + +/** + * A string identifying the type of the messaging operation. + * + * @note If a custom value is used, it **MUST** be of low cardinality. + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_MESSAGING_OPERATION_TYPE = 'messaging.operation.type' as const; + +/** + * The messaging system as identified by the client instrumentation. + * + * @note The actual messaging system may differ from the one known by the client. For example, when using Kafka client libraries to communicate with Azure Event Hubs, the `messaging.system` is set to `kafka` based on the instrumentation's best knowledge. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_MESSAGING_SYSTEM = 'messaging.system' as const; + +/** + * The name of the (logical) method being called, must be equal to the $method part in the span name. + * + * @example "exampleMethod" + * + * @note This is the logical name of the method from the RPC interface perspective, which can be different from the name of any implementing method/function. The `code.function.name` attribute may be used to store the latter (e.g., method actually executing the call on the server side, RPC client stub method on the client side). + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_RPC_METHOD = 'rpc.method' as const; + +/** + * The full (logical) name of the service being called, including its package name, if applicable. + * + * @example "myservice.EchoService" + * + * @note This is the logical name of the service from the RPC interface perspective, which can be different from the name of any implementing class. The `code.namespace` attribute may be used to store the latter (despite the attribute name, it may include a class name; e.g., class with method actually executing the call on the server side, RPC client stub class on the client side). + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_RPC_SERVICE = 'rpc.service' as const; + +/** + * A string identifying the remoting system. See below for a list of well-known identifiers. + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const ATTR_RPC_SYSTEM = 'rpc.system' as const; + +/** + * Enum value "dynamodb" for attribute {@link ATTR_DB_SYSTEM}. + * + * Amazon DynamoDB + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `DB_SYSTEM_NAME_VALUE_DYNAMODB`. + */ +export const DB_SYSTEM_VALUE_DYNAMODB = 'dynamodb' as const; + +/** + * Enum value "dynamodb" for attribute ATTR_DB_SYSTEM_NAME. + * + * Amazon DynamoDB + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const DB_SYSTEM_NAME_VALUE_DYNAMODB = 'dynamodb' as const; + +/** + * Enum value "chat" for attribute {@link ATTR_GEN_AI_OPERATION_NAME}. + * + * Chat completion operation such as [OpenAI Chat API](https://platform.openai.com/docs/api-reference/chat) + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const GEN_AI_OPERATION_NAME_VALUE_CHAT = 'chat' as const; + +/** + * Enum value "aws.bedrock" for attribute {@link ATTR_GEN_AI_SYSTEM}. + * + * AWS Bedrock + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const GEN_AI_SYSTEM_VALUE_AWS_BEDROCK = 'aws.bedrock' as const; + +/** + * Enum value "input" for attribute {@link ATTR_GEN_AI_TOKEN_TYPE}. + * + * Input tokens (prompt, input, etc.) + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const GEN_AI_TOKEN_TYPE_VALUE_INPUT = 'input' as const; + +/** + * Enum value "output" for attribute {@link ATTR_GEN_AI_TOKEN_TYPE}. + * + * Output tokens (completion, response, etc.) + * + * @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const GEN_AI_TOKEN_TYPE_VALUE_OUTPUT = 'output' as const; + +/** + * GenAI operation duration. + * + * @experimental This metric is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const METRIC_GEN_AI_CLIENT_OPERATION_DURATION = 'gen_ai.client.operation.duration' as const; + +/** + * Number of input and output tokens used. + * + * @experimental This metric is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + */ +export const METRIC_GEN_AI_CLIENT_TOKEN_USAGE = 'gen_ai.client.token.usage' as const; diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/MessageAttributes.ts b/packages/aws-serverless/src/integration/aws/vendored/services/MessageAttributes.ts new file mode 100644 index 000000000000..28c7abc18321 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/MessageAttributes.ts @@ -0,0 +1,100 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { TextMapGetter, TextMapSetter, context, propagation, diag } from '@opentelemetry/api'; +import type { SQS, SNS } from '../aws-sdk.types'; + +// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-quotas.html +export const MAX_MESSAGE_ATTRIBUTES = 10; +class ContextSetter implements TextMapSetter { + set(carrier: SQS.MessageBodyAttributeMap | SNS.MessageAttributeMap, key: string, value: string) { + carrier[key] = { + DataType: 'String', + StringValue: value as string, + }; + } +} +export const contextSetter = new ContextSetter(); + +export interface AwsSdkContextObject { + [key: string]: { + StringValue?: string; + Value?: string; + }; +} + +class ContextGetter implements TextMapGetter { + keys(carrier: SQS.MessageBodyAttributeMap | SNS.MessageAttributeMap): string[] { + if (carrier == null) { + return []; + } + return Object.keys(carrier); + } + + get(carrier: AwsSdkContextObject, key: string): undefined | string | string[] { + return carrier?.[key]?.StringValue || carrier?.[key]?.Value; + } +} +export const contextGetter = new ContextGetter(); + +export const injectPropagationContext = ( + attributesMap?: SQS.MessageBodyAttributeMap | SNS.MessageAttributeMap, +): SQS.MessageBodyAttributeMap | SNS.MessageAttributeMap => { + const attributes = attributesMap ?? {}; + if (Object.keys(attributes).length + propagation.fields().length <= MAX_MESSAGE_ATTRIBUTES) { + propagation.inject(context.active(), attributes, contextSetter); + } else { + diag.warn( + 'aws-sdk instrumentation: cannot set context propagation on SQS/SNS message due to maximum amount of MessageAttributes', + ); + } + return attributes; +}; + +export const extractPropagationContext = ( + message: SQS.Message, + sqsExtractContextPropagationFromPayload: boolean | undefined, +): AwsSdkContextObject | undefined => { + const propagationFields = propagation.fields(); + const hasPropagationFields = Object.keys(message.MessageAttributes || []).some(attr => + propagationFields.includes(attr), + ); + if (hasPropagationFields) { + return message.MessageAttributes; + } else if (sqsExtractContextPropagationFromPayload && message.Body) { + try { + const payload = JSON.parse(message.Body); + return payload.MessageAttributes; + } catch { + diag.debug('failed to parse SQS payload to extract context propagation, trace might be incomplete.'); + } + } + return undefined; +}; + +export const addPropagationFieldsToAttributeNames = ( + messageAttributeNames: string[] = [], + propagationFields: string[], +) => { + return messageAttributeNames.length + ? Array.from(new Set([...messageAttributeNames, ...propagationFields])) + : propagationFields; +}; diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/ServiceExtension.ts b/packages/aws-serverless/src/integration/aws/vendored/services/ServiceExtension.ts new file mode 100644 index 000000000000..50f47380784d --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/ServiceExtension.ts @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { DiagLogger, HrTime, Meter, Span, SpanAttributes, SpanKind, Tracer } from '@opentelemetry/api'; +import { SemconvStability } from '@opentelemetry/instrumentation'; +import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; + +export interface RequestMetadata { + // isIncoming - if true, then the operation callback / promise should be bind with the operation's span + isIncoming: boolean; + // isStream - if true, then the response is a stream so the span should not be ended by the middleware. + // the ServiceExtension must end the span itself, generally by wrapping the stream and ending after it is + // consumed. + isStream?: boolean; + spanAttributes?: SpanAttributes; + spanKind?: SpanKind; + spanName?: string; +} + +export interface ServiceExtension { + // called before request is sent, and before span is started + requestPreSpanHook: ( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + dbSemconvStability?: SemconvStability, + ) => RequestMetadata; + + // called before request is sent, and after span is started + requestPostSpanHook?: (request: NormalizedRequest) => void; + + // called after response is received. If value is returned, it replaces the response output. + responseHook?: ( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + startTime: HrTime, + ) => any | undefined; + + updateMetricInstruments?: (meter: Meter) => void; +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/ServicesExtensions.ts b/packages/aws-serverless/src/integration/aws/vendored/services/ServicesExtensions.ts new file mode 100644 index 000000000000..a7b7e6f2d05e --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/ServicesExtensions.ts @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Tracer, Span, DiagLogger, Meter, HrTime } from '@opentelemetry/api'; +import { SemconvStability } from '@opentelemetry/instrumentation'; +import { ServiceExtension, RequestMetadata } from './ServiceExtension'; +import { SqsServiceExtension } from './sqs'; +import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; +import { BedrockRuntimeServiceExtension } from './bedrock-runtime'; +import { DynamodbServiceExtension } from './dynamodb'; +import { SecretsManagerServiceExtension } from './secretsmanager'; +import { SnsServiceExtension } from './sns'; +import { StepFunctionsServiceExtension } from './stepfunctions'; +import { LambdaServiceExtension } from './lambda'; +import { S3ServiceExtension } from './s3'; +import { KinesisServiceExtension } from './kinesis'; + +export class ServicesExtensions implements ServiceExtension { + services: Map = new Map(); + + constructor() { + this.registerServices(); + } + + private registerServices() { + this.services.set('SecretsManager', new SecretsManagerServiceExtension()); + this.services.set('SFN', new StepFunctionsServiceExtension()); + this.services.set('SQS', new SqsServiceExtension()); + this.services.set('SNS', new SnsServiceExtension()); + this.services.set('DynamoDB', new DynamodbServiceExtension()); + this.services.set('Lambda', new LambdaServiceExtension()); + this.services.set('S3', new S3ServiceExtension()); + this.services.set('Kinesis', new KinesisServiceExtension()); + this.services.set('BedrockRuntime', new BedrockRuntimeServiceExtension()); + } + + requestPreSpanHook( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + dbSemconvStability?: SemconvStability, + ): RequestMetadata { + const serviceExtension = this.services.get(request.serviceName); + if (!serviceExtension) + return { + isIncoming: false, + }; + return serviceExtension.requestPreSpanHook(request, config, diag, dbSemconvStability); + } + + requestPostSpanHook(request: NormalizedRequest) { + const serviceExtension = this.services.get(request.serviceName); + if (!serviceExtension?.requestPostSpanHook) return; + return serviceExtension.requestPostSpanHook(request); + } + + responseHook( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + startTime: HrTime, + ) { + const serviceExtension = this.services.get(response.request.serviceName); + + return serviceExtension?.responseHook?.(response, span, tracer, config, startTime); + } + + updateMetricInstruments(meter: Meter) { + for (const serviceExtension of this.services.values()) { + serviceExtension.updateMetricInstruments?.(meter); + } + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts b/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts new file mode 100644 index 000000000000..ea106153f018 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts @@ -0,0 +1,636 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, DiagLogger, diag, Histogram, HrTime, Meter, Span, Tracer, ValueType } from '@opentelemetry/api'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; +import { + ATTR_GEN_AI_SYSTEM, + ATTR_GEN_AI_OPERATION_NAME, + ATTR_GEN_AI_REQUEST_MODEL, + ATTR_GEN_AI_REQUEST_MAX_TOKENS, + ATTR_GEN_AI_REQUEST_TEMPERATURE, + ATTR_GEN_AI_REQUEST_TOP_P, + ATTR_GEN_AI_REQUEST_STOP_SEQUENCES, + ATTR_GEN_AI_TOKEN_TYPE, + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + ATTR_GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_OPERATION_NAME_VALUE_CHAT, + GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + GEN_AI_TOKEN_TYPE_VALUE_INPUT, + GEN_AI_TOKEN_TYPE_VALUE_OUTPUT, + METRIC_GEN_AI_CLIENT_OPERATION_DURATION, + METRIC_GEN_AI_CLIENT_TOKEN_USAGE, +} from '../semconv'; +import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; +import { hrTime, hrTimeDuration, hrTimeToMilliseconds } from '@opentelemetry/core'; + +interface TokenUsage { + inputTokens: number | undefined; + outputTokens: number | undefined; + totalTokens: number | undefined; +} + +interface ConverseStreamOutput { + messageStop?: { stopReason?: string }; + metadata?: { usage?: TokenUsage }; + [key: string]: any; +} + +export class BedrockRuntimeServiceExtension implements ServiceExtension { + private tokenUsage!: Histogram; + private operationDuration!: Histogram; + private _diag: DiagLogger = diag; + + updateMetricInstruments(meter: Meter) { + // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclienttokenusage + this.tokenUsage = meter.createHistogram(METRIC_GEN_AI_CLIENT_TOKEN_USAGE, { + unit: '{token}', + description: 'Measures number of input and output tokens used', + valueType: ValueType.INT, + advice: { + explicitBucketBoundaries: [ + 1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, + ], + }, + }); + + // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclientoperationduration + this.operationDuration = meter.createHistogram(METRIC_GEN_AI_CLIENT_OPERATION_DURATION, { + unit: 's', + description: 'GenAI operation duration', + advice: { + explicitBucketBoundaries: [ + 0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.56, 5.12, 10.24, 20.48, 40.96, 81.92, + ], + }, + }); + } + + requestPreSpanHook( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + ): RequestMetadata { + switch (request.commandName) { + case 'Converse': + return this.requestPreSpanHookConverse(request, config, diag, false); + case 'ConverseStream': + return this.requestPreSpanHookConverse(request, config, diag, true); + case 'InvokeModel': + return this.requestPreSpanHookInvokeModel(request, config, diag, false); + case 'InvokeModelWithResponseStream': + return this.requestPreSpanHookInvokeModel(request, config, diag, true); + } + + return { + isIncoming: false, + }; + } + + private requestPreSpanHookConverse( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + isStream: boolean, + ): RequestMetadata { + let spanName = GEN_AI_OPERATION_NAME_VALUE_CHAT; + const spanAttributes: Attributes = { + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_OPERATION_NAME]: GEN_AI_OPERATION_NAME_VALUE_CHAT, + }; + + const modelId = request.commandInput.modelId; + if (modelId) { + spanAttributes[ATTR_GEN_AI_REQUEST_MODEL] = modelId; + if (spanName) { + spanName += ` ${modelId}`; + } + } + + const inferenceConfig = request.commandInput.inferenceConfig; + if (inferenceConfig) { + const { maxTokens, temperature, topP, stopSequences } = inferenceConfig; + if (maxTokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = maxTokens; + } + if (temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = temperature; + } + if (topP !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = topP; + } + if (stopSequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = stopSequences; + } + } + + return { + spanName, + isIncoming: false, + isStream, + spanAttributes, + }; + } + + private requestPreSpanHookInvokeModel( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + isStream: boolean, + ): RequestMetadata { + let spanName: string | undefined; + const spanAttributes: Attributes = { + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + // add operation name for InvokeModel API + }; + + const modelId = request.commandInput?.modelId; + if (modelId) { + spanAttributes[ATTR_GEN_AI_REQUEST_MODEL] = modelId; + } + + if (request.commandInput?.body) { + const requestBody = JSON.parse(request.commandInput.body); + if (modelId.includes('amazon.titan')) { + if (requestBody.textGenerationConfig?.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.textGenerationConfig.temperature; + } + if (requestBody.textGenerationConfig?.topP !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.textGenerationConfig.topP; + } + if (requestBody.textGenerationConfig?.maxTokenCount !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.textGenerationConfig.maxTokenCount; + } + if (requestBody.textGenerationConfig?.stopSequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = requestBody.textGenerationConfig.stopSequences; + } + } else if (modelId.includes('amazon.nova')) { + if (requestBody.inferenceConfig?.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.inferenceConfig.temperature; + } + if (requestBody.inferenceConfig?.top_p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.inferenceConfig.top_p; + } + if (requestBody.inferenceConfig?.max_new_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.inferenceConfig.max_new_tokens; + } + if (requestBody.inferenceConfig?.stopSequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = requestBody.inferenceConfig.stopSequences; + } + } else if (modelId.includes('anthropic.claude')) { + if (requestBody.max_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_tokens; + } + if (requestBody.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.temperature; + } + if (requestBody.top_p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.top_p; + } + if (requestBody.stop_sequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = requestBody.stop_sequences; + } + } else if (modelId.includes('meta.llama')) { + if (requestBody.max_gen_len !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_gen_len; + } + if (requestBody.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.temperature; + } + if (requestBody.top_p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.top_p; + } + // request for meta llama models does not contain stop_sequences field + } else if (modelId.includes('cohere.command-r')) { + if (requestBody.max_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_tokens; + } + if (requestBody.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.temperature; + } + if (requestBody.p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.p; + } + if (requestBody.message !== undefined) { + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + spanAttributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS] = Math.ceil(requestBody.message.length / 6); + } + if (requestBody.stop_sequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = requestBody.stop_sequences; + } + } else if (modelId.includes('cohere.command')) { + if (requestBody.max_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_tokens; + } + if (requestBody.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.temperature; + } + if (requestBody.p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.p; + } + if (requestBody.prompt !== undefined) { + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + spanAttributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS] = Math.ceil(requestBody.prompt.length / 6); + } + if (requestBody.stop_sequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = requestBody.stop_sequences; + } + } else if (modelId.includes('mistral')) { + if (requestBody.prompt !== undefined) { + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + spanAttributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS] = Math.ceil(requestBody.prompt.length / 6); + } + if (requestBody.max_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = requestBody.max_tokens; + } + if (requestBody.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = requestBody.temperature; + } + if (requestBody.top_p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.top_p; + } + if (requestBody.stop !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = requestBody.stop; + } + } + } + + return { + spanName, + isIncoming: false, + isStream, + spanAttributes, + }; + } + + responseHook( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + startTime: HrTime, + ) { + if (!span.isRecording()) { + return; + } + + switch (response.request.commandName) { + case 'Converse': + return this.responseHookConverse(response, span, tracer, config, startTime); + case 'ConverseStream': + return this.responseHookConverseStream(response, span, tracer, config, startTime); + case 'InvokeModel': + return this.responseHookInvokeModel(response, span, tracer, config); + case 'InvokeModelWithResponseStream': + return this.responseHookInvokeModelWithResponseStream(response, span, tracer, config); + } + } + + private responseHookConverse( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + startTime: HrTime, + ) { + const { stopReason, usage } = response.data; + + BedrockRuntimeServiceExtension.setStopReason(span, stopReason); + this.setUsage(response, span, usage, startTime); + } + + private responseHookConverseStream( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + startTime: HrTime, + ) { + return { + ...response.data, + // Wrap and replace the response stream to allow processing events to telemetry + // before yielding to the user. + stream: this.wrapConverseStreamResponse(response, response.data.stream, span, startTime), + }; + } + + private async *wrapConverseStreamResponse( + response: NormalizedResponse, + stream: AsyncIterable, + span: Span, + startTime: HrTime, + ) { + try { + let usage: TokenUsage | undefined; + for await (const item of stream) { + BedrockRuntimeServiceExtension.setStopReason(span, item.messageStop?.stopReason); + usage = item.metadata?.usage; + yield item; + } + this.setUsage(response, span, usage, startTime); + } finally { + span.end(); + } + } + + private static setStopReason(span: Span, stopReason: string | undefined) { + if (stopReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [stopReason]); + } + } + + private setUsage(response: NormalizedResponse, span: Span, usage: TokenUsage | undefined, startTime: HrTime) { + const sharedMetricAttrs: Attributes = { + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_OPERATION_NAME]: GEN_AI_OPERATION_NAME_VALUE_CHAT, + [ATTR_GEN_AI_REQUEST_MODEL]: response.request.commandInput.modelId, + }; + + const durationSecs = hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime())) / 1000; + this.operationDuration.record(durationSecs, sharedMetricAttrs); + + if (usage) { + const { inputTokens, outputTokens } = usage; + if (inputTokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, inputTokens); + + this.tokenUsage.record(inputTokens, { + ...sharedMetricAttrs, + [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_INPUT, + }); + } + if (outputTokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens); + + this.tokenUsage.record(outputTokens, { + ...sharedMetricAttrs, + [ATTR_GEN_AI_TOKEN_TYPE]: GEN_AI_TOKEN_TYPE_VALUE_OUTPUT, + }); + } + } + } + + private responseHookInvokeModel( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + ) { + const currentModelId = response.request.commandInput?.modelId; + if (response.data?.body) { + const decodedResponseBody = new TextDecoder().decode(response.data.body); + const responseBody = JSON.parse(decodedResponseBody); + if (currentModelId.includes('amazon.titan')) { + if (responseBody.inputTextTokenCount !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, responseBody.inputTextTokenCount); + } + if (responseBody.results?.[0]?.tokenCount !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, responseBody.results[0].tokenCount); + } + if (responseBody.results?.[0]?.completionReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.results[0].completionReason]); + } + } else if (currentModelId.includes('amazon.nova')) { + if (responseBody.usage !== undefined) { + if (responseBody.usage.inputTokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, responseBody.usage.inputTokens); + } + if (responseBody.usage.outputTokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, responseBody.usage.outputTokens); + } + } + if (responseBody.stopReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.stopReason]); + } + } else if (currentModelId.includes('anthropic.claude')) { + if (responseBody.usage?.input_tokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, responseBody.usage.input_tokens); + } + if (responseBody.usage?.output_tokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, responseBody.usage.output_tokens); + } + if (responseBody.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.stop_reason]); + } + } else if (currentModelId.includes('meta.llama')) { + if (responseBody.prompt_token_count !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, responseBody.prompt_token_count); + } + if (responseBody.generation_token_count !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, responseBody.generation_token_count); + } + if (responseBody.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.stop_reason]); + } + } else if (currentModelId.includes('cohere.command-r')) { + if (responseBody.text !== undefined) { + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, Math.ceil(responseBody.text.length / 6)); + } + if (responseBody.finish_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.finish_reason]); + } + } else if (currentModelId.includes('cohere.command')) { + if (responseBody.generations?.[0]?.text !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + Math.ceil(responseBody.generations[0].text.length / 6), + ); + } + if (responseBody.generations?.[0]?.finish_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.generations[0].finish_reason]); + } + } else if (currentModelId.includes('mistral')) { + if (responseBody.outputs?.[0]?.text !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + Math.ceil(responseBody.outputs[0].text.length / 6), + ); + } + if (responseBody.outputs?.[0]?.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [responseBody.outputs[0].stop_reason]); + } + } + } + } + + private async responseHookInvokeModelWithResponseStream( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig, + ): Promise { + const stream = response.data?.body; + const modelId = response.request.commandInput?.modelId; + if (!stream || !modelId) return; + + // Replace the original response body with our instrumented stream. + // - Defers span.end() until the entire stream is consumed + // This ensures downstream consumers still receive the full stream correctly, + // while OpenTelemetry can record span attributes from streamed data. + response.data.body = async function* (this: BedrockRuntimeServiceExtension) { + try { + for await (const chunk of stream) { + const parsedChunk = this.parseChunk(chunk?.chunk?.bytes); + + if (!parsedChunk) { + // pass through + } else if (modelId.includes('amazon.titan')) { + BedrockRuntimeServiceExtension.recordTitanAttributes(parsedChunk, span); + } else if (modelId.includes('anthropic.claude')) { + BedrockRuntimeServiceExtension.recordClaudeAttributes(parsedChunk, span); + } else if (modelId.includes('amazon.nova')) { + BedrockRuntimeServiceExtension.recordNovaAttributes(parsedChunk, span); + } else if (modelId.includes('meta.llama')) { + BedrockRuntimeServiceExtension.recordLlamaAttributes(parsedChunk, span); + } else if (modelId.includes('cohere.command-r')) { + BedrockRuntimeServiceExtension.recordCohereRAttributes(parsedChunk, span); + } else if (modelId.includes('cohere.command')) { + BedrockRuntimeServiceExtension.recordCohereAttributes(parsedChunk, span); + } else if (modelId.includes('mistral')) { + BedrockRuntimeServiceExtension.recordMistralAttributes(parsedChunk, span); + } + yield chunk; + } + } finally { + span.end(); + } + }.bind(this)(); + return response.data; + } + + private parseChunk(bytes?: Uint8Array): any { + if (!bytes || !(bytes instanceof Uint8Array)) return null; + try { + const str = Buffer.from(bytes).toString('utf-8'); + return JSON.parse(str); + } catch (err) { + this._diag.warn('Failed to parse streamed chunk', err); + return null; + } + } + + private static recordNovaAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.metadata?.usage !== undefined) { + if (parsedChunk.metadata?.usage.inputTokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, parsedChunk.metadata.usage.inputTokens); + } + if (parsedChunk.metadata?.usage.outputTokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, parsedChunk.metadata.usage.outputTokens); + } + } + if (parsedChunk.messageStop?.stopReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.messageStop.stopReason]); + } + } + + private static recordClaudeAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.message?.usage?.input_tokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, parsedChunk.message.usage.input_tokens); + } + if (parsedChunk.message?.usage?.output_tokens !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, parsedChunk.message.usage.output_tokens); + } + if (parsedChunk.delta?.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.delta.stop_reason]); + } + } + + private static recordTitanAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.inputTextTokenCount !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, parsedChunk.inputTextTokenCount); + } + if (parsedChunk.totalOutputTextTokenCount !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, parsedChunk.totalOutputTextTokenCount); + } + if (parsedChunk.completionReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.completionReason]); + } + } + private static recordLlamaAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.prompt_token_count !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, parsedChunk.prompt_token_count); + } + if (parsedChunk.generation_token_count !== undefined) { + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, parsedChunk.generation_token_count); + } + if (parsedChunk.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.stop_reason]); + } + } + + private static recordMistralAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.outputs?.[0]?.text !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + Math.ceil(parsedChunk.outputs[0].text.length / 6), + ); + } + if (parsedChunk.outputs?.[0]?.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.outputs[0].stop_reason]); + } + } + + private static recordCohereAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.generations?.[0]?.text !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + Math.ceil(parsedChunk.generations[0].text.length / 6), + ); + } + if (parsedChunk.generations?.[0]?.finish_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.generations[0].finish_reason]); + } + } + + private static recordCohereRAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.text !== undefined) { + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, Math.ceil(parsedChunk.text.length / 6)); + } + if (parsedChunk.finish_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [parsedChunk.finish_reason]); + } + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/dynamodb.ts b/packages/aws-serverless/src/integration/aws/vendored/services/dynamodb.ts new file mode 100644 index 000000000000..b7cb89cfd6d3 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/dynamodb.ts @@ -0,0 +1,255 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, DiagLogger, Span, SpanKind, Tracer } from '@opentelemetry/api'; +import { SemconvStability } from '@opentelemetry/instrumentation'; +import { + ATTR_DB_NAMESPACE, + ATTR_DB_OPERATION_NAME, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, +} from '@opentelemetry/semantic-conventions'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; +import { + ATTR_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS, + ATTR_AWS_DYNAMODB_CONSISTENT_READ, + ATTR_AWS_DYNAMODB_CONSUMED_CAPACITY, + ATTR_AWS_DYNAMODB_COUNT, + ATTR_AWS_DYNAMODB_EXCLUSIVE_START_TABLE, + ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES, + ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES, + ATTR_AWS_DYNAMODB_INDEX_NAME, + ATTR_AWS_DYNAMODB_ITEM_COLLECTION_METRICS, + ATTR_AWS_DYNAMODB_LIMIT, + ATTR_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES, + ATTR_AWS_DYNAMODB_PROJECTION, + ATTR_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY, + ATTR_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY, + ATTR_AWS_DYNAMODB_SCAN_FORWARD, + ATTR_AWS_DYNAMODB_SCANNED_COUNT, + ATTR_AWS_DYNAMODB_SEGMENT, + ATTR_AWS_DYNAMODB_SELECT, + ATTR_AWS_DYNAMODB_TABLE_COUNT, + ATTR_AWS_DYNAMODB_TABLE_NAMES, + ATTR_AWS_DYNAMODB_TOTAL_SEGMENTS, + ATTR_DB_NAME, + ATTR_DB_OPERATION, + ATTR_DB_STATEMENT, + ATTR_DB_SYSTEM, + DB_SYSTEM_NAME_VALUE_DYNAMODB, + DB_SYSTEM_VALUE_DYNAMODB, +} from '../semconv'; +import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; + +export class DynamodbServiceExtension implements ServiceExtension { + toArray(values: T | T[]): T[] { + return Array.isArray(values) ? values : [values]; + } + + requestPreSpanHook( + normalizedRequest: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + dbSemconvStability?: SemconvStability, + ): RequestMetadata { + const spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + const isIncoming = false; + const operation = normalizedRequest.commandName; + const tableName = normalizedRequest.commandInput?.TableName; + + const spanAttributes: Attributes = {}; + + if (dbSemconvStability === undefined || dbSemconvStability & SemconvStability.OLD) { + spanAttributes[ATTR_DB_SYSTEM] = DB_SYSTEM_VALUE_DYNAMODB; + spanAttributes[ATTR_DB_NAME] = tableName; + spanAttributes[ATTR_DB_OPERATION] = operation; + } + if (dbSemconvStability !== undefined && dbSemconvStability & SemconvStability.STABLE) { + spanAttributes[ATTR_DB_SYSTEM_NAME] = DB_SYSTEM_NAME_VALUE_DYNAMODB; + spanAttributes[ATTR_DB_NAMESPACE] = tableName; + spanAttributes[ATTR_DB_OPERATION_NAME] = operation; + } + + if (config.dynamoDBStatementSerializer) { + try { + const sanitizedStatement = config.dynamoDBStatementSerializer(operation, normalizedRequest.commandInput); + + if (typeof sanitizedStatement === 'string') { + if (dbSemconvStability === undefined || dbSemconvStability & SemconvStability.OLD) { + spanAttributes[ATTR_DB_STATEMENT] = sanitizedStatement; + } + if (dbSemconvStability !== undefined && dbSemconvStability & SemconvStability.STABLE) { + spanAttributes[ATTR_DB_QUERY_TEXT] = sanitizedStatement; + } + } + } catch (err) { + diag.error('failed to sanitize DynamoDB statement', err); + } + } + + // normalizedRequest.commandInput.RequestItems) is undefined when no table names are returned + // keys in this object are the table names + if (normalizedRequest.commandInput?.TableName) { + // Necessary for commands with only 1 table name (example: CreateTable). Attribute is TableName not keys of RequestItems + // single table name returned for operations like CreateTable + spanAttributes[ATTR_AWS_DYNAMODB_TABLE_NAMES] = [normalizedRequest.commandInput.TableName]; + } else if (normalizedRequest.commandInput?.RequestItems) { + spanAttributes[ATTR_AWS_DYNAMODB_TABLE_NAMES] = Object.keys(normalizedRequest.commandInput.RequestItems); + } + + if (operation === 'CreateTable' || operation === 'UpdateTable') { + // only check for ProvisionedThroughput since ReadCapacityUnits and WriteCapacity units are required attributes + if (normalizedRequest.commandInput?.ProvisionedThroughput) { + spanAttributes[ATTR_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY] = + normalizedRequest.commandInput.ProvisionedThroughput.ReadCapacityUnits; + spanAttributes[ATTR_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY] = + normalizedRequest.commandInput.ProvisionedThroughput.WriteCapacityUnits; + } + } + + if (operation === 'GetItem' || operation === 'Scan' || operation === 'Query') { + if (normalizedRequest.commandInput?.ConsistentRead) { + spanAttributes[ATTR_AWS_DYNAMODB_CONSISTENT_READ] = normalizedRequest.commandInput.ConsistentRead; + } + } + + if (operation === 'Query' || operation === 'Scan') { + if (normalizedRequest.commandInput?.ProjectionExpression) { + spanAttributes[ATTR_AWS_DYNAMODB_PROJECTION] = normalizedRequest.commandInput.ProjectionExpression; + } + } + + if (operation === 'CreateTable') { + if (normalizedRequest.commandInput?.GlobalSecondaryIndexes) { + spanAttributes[ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES] = this.toArray( + normalizedRequest.commandInput.GlobalSecondaryIndexes, + ).map((x: { [DictionaryKey: string]: any }) => JSON.stringify(x)); + } + + if (normalizedRequest.commandInput?.LocalSecondaryIndexes) { + spanAttributes[ATTR_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES] = this.toArray( + normalizedRequest.commandInput.LocalSecondaryIndexes, + ).map((x: { [DictionaryKey: string]: any }) => JSON.stringify(x)); + } + } + + if (operation === 'ListTables' || operation === 'Query' || operation === 'Scan') { + if (normalizedRequest.commandInput?.Limit) { + spanAttributes[ATTR_AWS_DYNAMODB_LIMIT] = normalizedRequest.commandInput.Limit; + } + } + + if (operation === 'ListTables') { + if (normalizedRequest.commandInput?.ExclusiveStartTableName) { + spanAttributes[ATTR_AWS_DYNAMODB_EXCLUSIVE_START_TABLE] = + normalizedRequest.commandInput.ExclusiveStartTableName; + } + } + + if (operation === 'Query') { + if (normalizedRequest.commandInput?.ScanIndexForward) { + spanAttributes[ATTR_AWS_DYNAMODB_SCAN_FORWARD] = normalizedRequest.commandInput.ScanIndexForward; + } + + if (normalizedRequest.commandInput?.IndexName) { + spanAttributes[ATTR_AWS_DYNAMODB_INDEX_NAME] = normalizedRequest.commandInput.IndexName; + } + + if (normalizedRequest.commandInput?.Select) { + spanAttributes[ATTR_AWS_DYNAMODB_SELECT] = normalizedRequest.commandInput.Select; + } + } + + if (operation === 'Scan') { + if (normalizedRequest.commandInput?.Segment) { + spanAttributes[ATTR_AWS_DYNAMODB_SEGMENT] = normalizedRequest.commandInput?.Segment; + } + + if (normalizedRequest.commandInput?.TotalSegments) { + spanAttributes[ATTR_AWS_DYNAMODB_TOTAL_SEGMENTS] = normalizedRequest.commandInput?.TotalSegments; + } + + if (normalizedRequest.commandInput?.IndexName) { + spanAttributes[ATTR_AWS_DYNAMODB_INDEX_NAME] = normalizedRequest.commandInput.IndexName; + } + + if (normalizedRequest.commandInput?.Select) { + spanAttributes[ATTR_AWS_DYNAMODB_SELECT] = normalizedRequest.commandInput.Select; + } + } + + if (operation === 'UpdateTable') { + if (normalizedRequest.commandInput?.AttributeDefinitions) { + spanAttributes[ATTR_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS] = this.toArray( + normalizedRequest.commandInput.AttributeDefinitions, + ).map((x: { [DictionaryKey: string]: any }) => JSON.stringify(x)); + } + + if (normalizedRequest.commandInput?.GlobalSecondaryIndexUpdates) { + spanAttributes[ATTR_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES] = this.toArray( + normalizedRequest.commandInput.GlobalSecondaryIndexUpdates, + ).map((x: { [DictionaryKey: string]: any }) => JSON.stringify(x)); + } + } + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } + + responseHook(response: NormalizedResponse, span: Span, _tracer: Tracer, _config: AwsSdkInstrumentationConfig) { + if (response.data?.ConsumedCapacity) { + span.setAttribute( + ATTR_AWS_DYNAMODB_CONSUMED_CAPACITY, + toArray(response.data.ConsumedCapacity).map((x: { [DictionaryKey: string]: any }) => JSON.stringify(x)), + ); + } + + if (response.data?.ItemCollectionMetrics) { + span.setAttribute( + ATTR_AWS_DYNAMODB_ITEM_COLLECTION_METRICS, + this.toArray(response.data.ItemCollectionMetrics).map((x: { [DictionaryKey: string]: any }) => + JSON.stringify(x), + ), + ); + } + + if (response.data?.TableNames) { + span.setAttribute(ATTR_AWS_DYNAMODB_TABLE_COUNT, response.data?.TableNames.length); + } + + if (response.data?.Count) { + span.setAttribute(ATTR_AWS_DYNAMODB_COUNT, response.data?.Count); + } + + if (response.data?.ScannedCount) { + span.setAttribute(ATTR_AWS_DYNAMODB_SCANNED_COUNT, response.data?.ScannedCount); + } + } +} + +function toArray(values: T | T[]): T[] { + return Array.isArray(values) ? values : [values]; +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/index.ts b/packages/aws-serverless/src/integration/aws/vendored/services/index.ts new file mode 100644 index 000000000000..ea79e34b2e93 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +export { ServicesExtensions } from './ServicesExtensions'; diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/kinesis.ts b/packages/aws-serverless/src/integration/aws/vendored/services/kinesis.ts new file mode 100644 index 000000000000..fc2248d2fde8 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/kinesis.ts @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, SpanKind } from '@opentelemetry/api'; +import { AttributeNames } from '../enums'; +import { AwsSdkInstrumentationConfig, NormalizedRequest } from '../types'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; + +export class KinesisServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + const streamName = request.commandInput?.StreamName; + const spanKind: SpanKind = SpanKind.CLIENT; + const spanAttributes: Attributes = {}; + + if (streamName) { + spanAttributes[AttributeNames.AWS_KINESIS_STREAM_NAME] = streamName; + } + + const isIncoming = false; + + return { + isIncoming, + spanAttributes, + spanKind, + }; + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/lambda.ts b/packages/aws-serverless/src/integration/aws/vendored/services/lambda.ts new file mode 100644 index 000000000000..988b0027ffcd --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/lambda.ts @@ -0,0 +1,118 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Span, SpanKind, Tracer, diag, Attributes } from '@opentelemetry/api'; +import { ATTR_FAAS_INVOKED_NAME, ATTR_FAAS_INVOKED_PROVIDER, ATTR_FAAS_INVOKED_REGION } from '../semconv'; +import { ATTR_FAAS_EXECUTION } from '../semconv-obsolete'; +import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; +import { context, propagation } from '@opentelemetry/api'; + +class LambdaCommands { + public static readonly Invoke: string = 'Invoke'; +} + +export class LambdaServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + const functionName = this.extractFunctionName(request.commandInput); + + let spanAttributes: Attributes = {}; + let spanName: string | undefined; + + switch (request.commandName) { + case 'Invoke': + spanAttributes = { + [ATTR_FAAS_INVOKED_NAME]: functionName, + [ATTR_FAAS_INVOKED_PROVIDER]: 'aws', + }; + if (request.region) { + spanAttributes[ATTR_FAAS_INVOKED_REGION] = request.region; + } + spanName = `${functionName} ${LambdaCommands.Invoke}`; + break; + } + return { + isIncoming: false, + spanAttributes, + spanKind: SpanKind.CLIENT, + spanName, + }; + } + + requestPostSpanHook = (request: NormalizedRequest) => { + switch (request.commandName) { + case LambdaCommands.Invoke: + { + if (request.commandInput) { + request.commandInput.ClientContext = injectLambdaPropagationContext(request.commandInput.ClientContext); + } + } + break; + } + }; + + responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig) { + switch (response.request.commandName) { + case LambdaCommands.Invoke: + { + span.setAttribute(ATTR_FAAS_EXECUTION, response.requestId); + } + break; + } + } + + extractFunctionName = (commandInput: Record): string => { + return commandInput?.FunctionName; + }; +} + +const injectLambdaPropagationContext = (clientContext: string | undefined): string | undefined => { + try { + const propagatedContext = {}; + propagation.inject(context.active(), propagatedContext); + + const parsedClientContext = clientContext ? JSON.parse(Buffer.from(clientContext, 'base64').toString('utf8')) : {}; + + const updatedClientContext = { + ...parsedClientContext, + custom: { + ...parsedClientContext.custom, + ...propagatedContext, + }, + }; + + const encodedClientContext = Buffer.from(JSON.stringify(updatedClientContext)).toString('base64'); + + // The length of client context is capped at 3583 bytes of base64 encoded data + // (https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestSyntax) + if (encodedClientContext.length > 3583) { + diag.warn( + 'lambda instrumentation: cannot set context propagation on lambda invoke parameters due to ClientContext length limitations.', + ); + return clientContext; + } + + return encodedClientContext; + } catch (e) { + diag.debug('lambda instrumentation: failed to set context propagation on ClientContext', e); + return clientContext; + } +}; diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/s3.ts b/packages/aws-serverless/src/integration/aws/vendored/services/s3.ts new file mode 100644 index 000000000000..07b6ebf4458f --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/s3.ts @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, SpanKind } from '@opentelemetry/api'; +import { AttributeNames } from '../enums'; +import { AwsSdkInstrumentationConfig, NormalizedRequest } from '../types'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; + +export class S3ServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + const bucketName = request.commandInput?.Bucket; + const spanKind: SpanKind = SpanKind.CLIENT; + const spanAttributes: Attributes = {}; + + if (bucketName) { + spanAttributes[AttributeNames.AWS_S3_BUCKET] = bucketName; + } + + const isIncoming = false; + + return { + isIncoming, + spanAttributes, + spanKind, + }; + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/secretsmanager.ts b/packages/aws-serverless/src/integration/aws/vendored/services/secretsmanager.ts new file mode 100644 index 000000000000..193f5b37a5cc --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/secretsmanager.ts @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, Span, SpanKind, Tracer } from '@opentelemetry/api'; +import { ATTR_AWS_SECRETSMANAGER_SECRET_ARN } from '../semconv'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; +import { NormalizedRequest, NormalizedResponse, AwsSdkInstrumentationConfig } from '../types'; + +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[ATTR_AWS_SECRETSMANAGER_SECRET_ARN] = secretId; + } + + return { + isIncoming: false, + spanAttributes, + spanKind, + spanName, + }; + } + + responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void { + const secretArn = response.data?.ARN; + if (secretArn) { + span.setAttribute(ATTR_AWS_SECRETSMANAGER_SECRET_ARN, secretArn); + } + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/sns.ts b/packages/aws-serverless/src/integration/aws/vendored/services/sns.ts new file mode 100644 index 000000000000..a040cf4f7070 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/sns.ts @@ -0,0 +1,96 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Span, Tracer, SpanKind, Attributes } from '@opentelemetry/api'; +import { ATTR_AWS_SNS_TOPIC_ARN, ATTR_MESSAGING_SYSTEM } from '../semconv'; +import { + ATTR_MESSAGING_DESTINATION, + ATTR_MESSAGING_DESTINATION_KIND, + MESSAGING_DESTINATION_KIND_VALUE_TOPIC, +} from '../semconv-obsolete'; +import { NormalizedRequest, NormalizedResponse, AwsSdkInstrumentationConfig } from '../types'; +import { injectPropagationContext } from './MessageAttributes'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; + +export class SnsServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + let spanKind: SpanKind = SpanKind.CLIENT; + let spanName = `SNS ${request.commandName}`; + const spanAttributes: Attributes = { + [ATTR_MESSAGING_SYSTEM]: 'aws.sns', + }; + + if (request.commandName === 'Publish') { + spanKind = SpanKind.PRODUCER; + + spanAttributes[ATTR_MESSAGING_DESTINATION_KIND] = MESSAGING_DESTINATION_KIND_VALUE_TOPIC; + const { TopicArn, TargetArn, PhoneNumber } = request.commandInput; + spanAttributes[ATTR_MESSAGING_DESTINATION] = this.extractDestinationName(TopicArn, TargetArn, PhoneNumber); + // ToDO: Use ATTR_MESSAGING_DESTINATION_NAME when implemented + spanAttributes['messaging.destination.name'] = TopicArn || TargetArn || PhoneNumber || 'unknown'; + + spanName = `${PhoneNumber ? 'phone_number' : spanAttributes[ATTR_MESSAGING_DESTINATION]} send`; + } + + const topicArn = request.commandInput?.TopicArn; + if (topicArn) { + spanAttributes[ATTR_AWS_SNS_TOPIC_ARN] = topicArn; + } + + return { + isIncoming: false, + spanAttributes, + spanKind, + spanName, + }; + } + + requestPostSpanHook(request: NormalizedRequest): void { + if (request.commandName === 'Publish') { + const origMessageAttributes = request.commandInput['MessageAttributes'] ?? {}; + if (origMessageAttributes) { + request.commandInput['MessageAttributes'] = injectPropagationContext(origMessageAttributes); + } + } + } + + responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void { + const topicArn = response.data?.TopicArn; + if (topicArn) { + span.setAttribute(ATTR_AWS_SNS_TOPIC_ARN, topicArn); + } + } + + extractDestinationName(topicArn: string, targetArn: string, phoneNumber: string): string { + if (topicArn || targetArn) { + const arn = topicArn ?? targetArn; + try { + return arn.substring(arn.lastIndexOf(':') + 1); + } catch (err) { + return arn; + } + } else if (phoneNumber) { + return phoneNumber; + } else { + return 'unknown'; + } + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/sqs.ts b/packages/aws-serverless/src/integration/aws/vendored/services/sqs.ts new file mode 100644 index 000000000000..7bab11acafb1 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/sqs.ts @@ -0,0 +1,160 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Tracer, SpanKind, Span, propagation, trace, ROOT_CONTEXT, Attributes } from '@opentelemetry/api'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; +import type { SQS } from '../aws-sdk.types'; +import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; +import { ATTR_URL_FULL } from '@opentelemetry/semantic-conventions'; +import { + ATTR_MESSAGING_BATCH_MESSAGE_COUNT, + ATTR_MESSAGING_DESTINATION_NAME, + ATTR_MESSAGING_MESSAGE_ID, + ATTR_MESSAGING_OPERATION_TYPE, + ATTR_MESSAGING_SYSTEM, +} from '../semconv'; +import { + contextGetter, + extractPropagationContext, + injectPropagationContext, + addPropagationFieldsToAttributeNames, +} from './MessageAttributes'; + +export class SqsServiceExtension implements ServiceExtension { + requestPreSpanHook(request: NormalizedRequest, _config: AwsSdkInstrumentationConfig): RequestMetadata { + const queueUrl = this.extractQueueUrl(request.commandInput); + const queueName = this.extractQueueNameFromUrl(queueUrl); + let spanKind: SpanKind = SpanKind.CLIENT; + let spanName: string | undefined; + + const spanAttributes: Attributes = { + [ATTR_MESSAGING_SYSTEM]: 'aws_sqs', + [ATTR_MESSAGING_DESTINATION_NAME]: queueName, + [ATTR_URL_FULL]: queueUrl, + }; + + let isIncoming = false; + + switch (request.commandName) { + case 'ReceiveMessage': + { + isIncoming = true; + spanKind = SpanKind.CONSUMER; + spanName = `${queueName} receive`; + spanAttributes[ATTR_MESSAGING_OPERATION_TYPE] = 'receive'; + + request.commandInput.MessageAttributeNames = addPropagationFieldsToAttributeNames( + request.commandInput.MessageAttributeNames, + propagation.fields(), + ); + } + break; + + case 'SendMessage': + case 'SendMessageBatch': + spanKind = SpanKind.PRODUCER; + spanName = `${queueName} send`; + break; + } + + return { + isIncoming, + spanAttributes, + spanKind, + spanName, + }; + } + + requestPostSpanHook = (request: NormalizedRequest) => { + switch (request.commandName) { + case 'SendMessage': + { + const origMessageAttributes = request.commandInput['MessageAttributes'] ?? {}; + if (origMessageAttributes) { + request.commandInput['MessageAttributes'] = injectPropagationContext(origMessageAttributes); + } + } + break; + + case 'SendMessageBatch': + { + const entries = request.commandInput?.Entries; + if (Array.isArray(entries)) { + entries.forEach((messageParams: { MessageAttributes: SQS.MessageBodyAttributeMap }) => { + messageParams.MessageAttributes = injectPropagationContext(messageParams.MessageAttributes ?? {}); + }); + } + } + break; + } + }; + + responseHook = (response: NormalizedResponse, span: Span, _tracer: Tracer, config: AwsSdkInstrumentationConfig) => { + switch (response.request.commandName) { + case 'SendMessage': + span.setAttribute(ATTR_MESSAGING_MESSAGE_ID, response?.data?.MessageId); + break; + + case 'SendMessageBatch': + // TODO: How should this be handled? + break; + + case 'ReceiveMessage': { + const messages: SQS.Message[] = response?.data?.Messages || []; + + span.setAttribute(ATTR_MESSAGING_BATCH_MESSAGE_COUNT, messages.length); + + for (const message of messages) { + const propagatedContext = propagation.extract( + ROOT_CONTEXT, + extractPropagationContext(message, config.sqsExtractContextPropagationFromPayload), + contextGetter, + ); + + const spanContext = trace.getSpanContext(propagatedContext); + + if (spanContext) { + span.addLink({ + context: spanContext, + attributes: { + [ATTR_MESSAGING_MESSAGE_ID]: message.MessageId, + }, + }); + } + } + break; + } + } + }; + + extractQueueUrl = (commandInput: Record): string => { + return commandInput?.QueueUrl; + }; + + extractQueueNameFromUrl = (queueUrl: string): string | undefined => { + if (!queueUrl) return undefined; + + const segments = queueUrl.split('/'); + if (segments.length === 0) return undefined; + + return segments[segments.length - 1]; + }; +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/stepfunctions.ts b/packages/aws-serverless/src/integration/aws/vendored/services/stepfunctions.ts new file mode 100644 index 000000000000..92da61e4a9b2 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/services/stepfunctions.ts @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, SpanKind } from '@opentelemetry/api'; +import { ATTR_AWS_STEP_FUNCTIONS_ACTIVITY_ARN, ATTR_AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN } from '../semconv'; +import { RequestMetadata, ServiceExtension } from './ServiceExtension'; +import { NormalizedRequest, AwsSdkInstrumentationConfig } from '../types'; + +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; + const spanAttributes: Attributes = {}; + + if (stateMachineArn) { + spanAttributes[ATTR_AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN] = stateMachineArn; + } + + if (activityArn) { + spanAttributes[ATTR_AWS_STEP_FUNCTIONS_ACTIVITY_ARN] = activityArn; + } + + return { + isIncoming: false, + spanAttributes, + spanKind, + }; + } +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/types.ts b/packages/aws-serverless/src/integration/aws/vendored/types.ts new file mode 100644 index 000000000000..54270c005965 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/types.ts @@ -0,0 +1,112 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Span } from '@opentelemetry/api'; +import { InstrumentationConfig } from '@opentelemetry/instrumentation'; + +export type CommandInput = Record; + +/** + * These are normalized request and response. + * They organize the relevant data in one interface which can be processed in a + * uniform manner in hooks + */ +export interface NormalizedRequest { + serviceName: string; + commandName: string; + commandInput: CommandInput; + region?: string; +} +export interface NormalizedResponse { + data: any; + request: NormalizedRequest; + requestId: string; +} + +export interface AwsSdkRequestHookInformation { + moduleVersion?: string; + request: NormalizedRequest; +} +export interface AwsSdkRequestCustomAttributeFunction { + (span: Span, requestInfo: AwsSdkRequestHookInformation): void; +} + +export interface AwsSdkResponseHookInformation { + moduleVersion?: string; + response: NormalizedResponse; +} + +/** + * span can be used to add custom attributes, or for any other need. + * response is the object that is returned to the user calling the aws-sdk operation. + * The response type and attributes on the response are client-specific. + */ +export interface AwsSdkResponseCustomAttributeFunction { + (span: Span, responseInfo: AwsSdkResponseHookInformation): void; +} + +/** + * span can be used to modify the status. + * As we have no response object, the request can be used to determine the failed aws-sdk operation. + */ +export interface AwsSdkExceptionCustomAttributeFunction { + (span: Span, requestInfo: AwsSdkRequestHookInformation, err: any): void; +} + +export type AwsSdkDynamoDBStatementSerializer = (operation: string, commandInput: CommandInput) => string | undefined; + +export interface AwsSdkInstrumentationConfig extends InstrumentationConfig { + /** hook for adding custom attributes before request is sent to aws */ + preRequestHook?: AwsSdkRequestCustomAttributeFunction; + + /** hook for adding custom attributes when response is received from aws */ + responseHook?: AwsSdkResponseCustomAttributeFunction; + + /** + * Hook for adding custom attributes when exception is received from aws. + * This hook is only available with aws sdk v3 + */ + exceptionHook?: AwsSdkExceptionCustomAttributeFunction; + + /** custom serializer function for the db.statement attribute in DynamoDB spans */ + dynamoDBStatementSerializer?: AwsSdkDynamoDBStatementSerializer; + + /** + * Most aws operation use http request under the hood. + * if http instrumentation is enabled, each aws operation will also create + * an http/s child describing the communication with amazon servers. + * Setting the `suppressInternalInstrumentation` config value to `true` will + * cause the instrumentation to suppress instrumentation of underlying operations, + * effectively causing those http spans to be non-recordable. + */ + suppressInternalInstrumentation?: boolean; + + /** + * In some cases the context propagation headers may be found in the message payload + * rather than the message attribute. + * When this field is turned on the instrumentation will parse the payload and extract the + * context from there. + * Even if the field is on and MessageAttribute contains context propagation field are present, + * the MessageAttribute will get priority. + * By default it is off. + */ + sqsExtractContextPropagationFromPayload?: boolean; +} diff --git a/packages/aws-serverless/src/integration/aws/vendored/utils.ts b/packages/aws-serverless/src/integration/aws/vendored/utils.ts new file mode 100644 index 000000000000..fc140720d5a8 --- /dev/null +++ b/packages/aws-serverless/src/integration/aws/vendored/utils.ts @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-aws-sdk + * - Upstream version: @opentelemetry/instrumentation-aws-sdk@0.73.0 + */ +/* eslint-disable */ + +import { Attributes, Context, context } from '@opentelemetry/api'; +import { ATTR_RPC_METHOD, ATTR_RPC_SERVICE, ATTR_RPC_SYSTEM } from './semconv'; +import { AttributeNames } from './enums'; +import { NormalizedRequest } from './types'; + +export const removeSuffixFromStringIfExists = (str: string, suffixToRemove: string): string => { + const suffixLength = suffixToRemove.length; + return str?.slice(-suffixLength) === suffixToRemove ? str.slice(0, str.length - suffixLength) : str; +}; + +export const normalizeV3Request = ( + serviceName: string, + commandNameWithSuffix: string, + commandInput: Record, + region: string | undefined, +): NormalizedRequest => { + return { + serviceName: serviceName?.replace(/\s+/g, ''), + commandName: removeSuffixFromStringIfExists(commandNameWithSuffix, 'Command'), + commandInput, + region, + }; +}; + +export const extractAttributesFromNormalizedRequest = (normalizedRequest: NormalizedRequest): Attributes => { + return { + [ATTR_RPC_SYSTEM]: 'aws-api', + [ATTR_RPC_METHOD]: normalizedRequest.commandName, + [ATTR_RPC_SERVICE]: normalizedRequest.serviceName, + [AttributeNames.CLOUD_REGION]: normalizedRequest.region, + }; +}; + +export const bindPromise = ( + target: Promise, + contextForCallbacks: Context, + rebindCount = 1, +): Promise => { + const origThen = target.then; + type PromiseThenParameters = Parameters['then']>; + target.then = function ( + onFulfilled: PromiseThenParameters[0], + onRejected: PromiseThenParameters[1], + ): Promise { + const newOnFulfilled = context.bind(contextForCallbacks, onFulfilled); + const newOnRejected = context.bind(contextForCallbacks, onRejected); + const patchedPromise = (origThen.call as any)( + this, + newOnFulfilled, + newOnRejected, + ); + return rebindCount > 1 ? bindPromise(patchedPromise, contextForCallbacks, rebindCount - 1) : patchedPromise; + }; + return target; +}; diff --git a/yarn.lock b/yarn.lock index 6bf617d827c9..0dcadd3a22cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6067,15 +6067,6 @@ "@opentelemetry/instrumentation" "^0.214.0" "@opentelemetry/semantic-conventions" "^1.33.0" -"@opentelemetry/instrumentation-aws-sdk@0.69.0": - version "0.69.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.69.0.tgz#461de2337b1931c195f0d284760206a657bdee06" - integrity sha512-JfSp3anFL5Lx/ysQSa4MnKxvSsXSnYpgQ831Y+yNs5wJZcJC4tB+YpnKH+bU5oFdKEF59FpI6Gn5Wg2vjVpR2A== - dependencies: - "@opentelemetry/core" "^2.0.0" - "@opentelemetry/instrumentation" "^0.214.0" - "@opentelemetry/semantic-conventions" "^1.34.0" - "@opentelemetry/instrumentation-connect@0.57.0": version "0.57.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.57.0.tgz#66b58af135ef6d52ad546cb440b808a149118296" From a6f348b4c26c05b7daf0d2b106e6660341f64008 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 18 May 2026 15:06:02 +0200 Subject: [PATCH 2/6] ref: Add comment explaining inlined AWS SDK types Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/integration/aws/vendored/services/bedrock-runtime.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts b/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts index ea106153f018..ff5f29e71b88 100644 --- a/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts +++ b/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts @@ -43,6 +43,8 @@ import { import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; import { hrTime, hrTimeDuration, hrTimeToMilliseconds } from '@opentelemetry/core'; +// Simplified types inlined from @aws-sdk/client-bedrock-runtime to avoid requiring the package. +// Only the fields accessed by this instrumentation are included. interface TokenUsage { inputTokens: number | undefined; outputTokens: number | undefined; From 0d8ee4bdd8a2cf36cb213a014e269f3764196780 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 18 May 2026 15:06:47 +0200 Subject: [PATCH 3/6] clean stuff --- .../integration/aws/vendored/services/bedrock-runtime.ts | 4 ++-- .../aws-serverless/src/integration/aws/vendored/utils.ts | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts b/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts index ff5f29e71b88..77e9ada8960f 100644 --- a/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts +++ b/packages/aws-serverless/src/integration/aws/vendored/services/bedrock-runtime.ts @@ -43,8 +43,8 @@ import { import { AwsSdkInstrumentationConfig, NormalizedRequest, NormalizedResponse } from '../types'; import { hrTime, hrTimeDuration, hrTimeToMilliseconds } from '@opentelemetry/core'; -// Simplified types inlined from @aws-sdk/client-bedrock-runtime to avoid requiring the package. -// Only the fields accessed by this instrumentation are included. +// Simplified types inlined from @aws-sdk/client-bedrock-runtime +// Only the fields accessed by this instrumentation are included interface TokenUsage { inputTokens: number | undefined; outputTokens: number | undefined; diff --git a/packages/aws-serverless/src/integration/aws/vendored/utils.ts b/packages/aws-serverless/src/integration/aws/vendored/utils.ts index fc140720d5a8..2ebefcbe2745 100644 --- a/packages/aws-serverless/src/integration/aws/vendored/utils.ts +++ b/packages/aws-serverless/src/integration/aws/vendored/utils.ts @@ -65,11 +65,7 @@ export const bindPromise = ( ): Promise { const newOnFulfilled = context.bind(contextForCallbacks, onFulfilled); const newOnRejected = context.bind(contextForCallbacks, onRejected); - const patchedPromise = (origThen.call as any)( - this, - newOnFulfilled, - newOnRejected, - ); + const patchedPromise = (origThen.call as any)(this, newOnFulfilled, newOnRejected); return rebindCount > 1 ? bindPromise(patchedPromise, contextForCallbacks, rebindCount - 1) : patchedPromise; }; return target; From 9657ba6484ee98a5e6272bb93d3f68312891d0ba Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 18 May 2026 18:47:34 +0200 Subject: [PATCH 4/6] ref: Add preserveModulesRoot to fix build output paths Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/aws-serverless/rollup.npm.config.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/aws-serverless/rollup.npm.config.mjs b/packages/aws-serverless/rollup.npm.config.mjs index 0ac3218144d5..7f08d44dfcdb 100644 --- a/packages/aws-serverless/rollup.npm.config.mjs +++ b/packages/aws-serverless/rollup.npm.config.mjs @@ -12,6 +12,9 @@ export default [ packageSpecificConfig: { // Used for our custom eventContextExtractor external: ['@opentelemetry/api'], + output: { + preserveModulesRoot: 'src', + }, }, }), ), From d98ceae7ec88841ca47909c0094469bc9471270b Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 19 May 2026 12:11:19 +0200 Subject: [PATCH 5/6] ref: Add @opentelemetry/core as direct dependency Previously a transitive dependency of @opentelemetry/instrumentation-aws-sdk, now needed directly by the vendored code. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/aws-serverless/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 2f4ed3fcf921..95d550a12827 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -67,6 +67,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.1", + "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry/core": "10.53.1", From 5a671b6e4975f40c5b2587af41144f2a95e4430e Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 20 May 2026 08:14:24 +0200 Subject: [PATCH 6/6] Update .oxlintrc.base.json --- .oxlintrc.base.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.oxlintrc.base.json b/.oxlintrc.base.json index e5f9fa29e2cf..afbf60ab6c4a 100644 --- a/.oxlintrc.base.json +++ b/.oxlintrc.base.json @@ -148,7 +148,7 @@ "**/integrations/tracing/knex/vendored/**/*.ts", "**/integrations/tracing/mongo/vendored/**/*.ts", "**/integrations/tracing/connect/vendored/**/*.ts", - "**/integration/aws/vendored/**/*.ts" + "**/integration/aws/vendored/**/*.ts", "**/integrations/tracing/tedious/vendored/**/*.ts" ], "rules": {