diff --git a/package-lock.json b/package-lock.json index a2d0a16715..d1be96adcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14377,37 +14377,6 @@ "fastq": "^1.17.1" } }, - "node_modules/aws-sdk": { - "version": "2.1008.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1008.0.tgz", - "integrity": "sha512-wBEon+ARCuMcwEPpOFZqRT3elBLfLtPqv8jMql3Hsr7Ua5toPlgKMmjf368iTzBNaY7TOZsjLAT9nAhvtSZ++g==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.15.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-sdk/node_modules/uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -14961,17 +14930,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -18313,15 +18271,6 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, - "node_modules/events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/ewma": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ewma/-/ewma-2.0.1.tgz", @@ -21958,15 +21907,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/joi": { "version": "17.12.2", "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", @@ -29425,16 +29365,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -30893,12 +30823,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "devOptional": true }, - "node_modules/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", - "dev": true - }, "node_modules/scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -34281,16 +34205,6 @@ "node": ">=6" } }, - "node_modules/url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -34301,12 +34215,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", - "dev": true - }, "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", @@ -35204,25 +35112,6 @@ } } }, - "node_modules/xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -37360,7 +37249,6 @@ "@types/mocha": "8.2.3", "@types/node": "18.18.14", "@types/sinon": "10.0.20", - "aws-sdk": "2.1008.0", "eslint": "8.7.0", "expect": "29.2.0", "nock": "13.3.3", @@ -47370,7 +47258,6 @@ "@types/mocha": "8.2.3", "@types/node": "18.18.14", "@types/sinon": "10.0.20", - "aws-sdk": "2.1008.0", "eslint": "8.7.0", "expect": "29.2.0", "nock": "13.3.3", @@ -54123,31 +54010,6 @@ "fastq": "^1.17.1" } }, - "aws-sdk": { - "version": "2.1008.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1008.0.tgz", - "integrity": "sha512-wBEon+ARCuMcwEPpOFZqRT3elBLfLtPqv8jMql3Hsr7Ua5toPlgKMmjf368iTzBNaY7TOZsjLAT9nAhvtSZ++g==", - "dev": true, - "requires": { - "buffer": "4.9.2", - "events": "1.1.1", - "ieee754": "1.1.13", - "jmespath": "0.15.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" - }, - "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - } - } - }, "aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -54579,17 +54441,6 @@ } } }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -57151,12 +57002,6 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", - "dev": true - }, "ewma": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ewma/-/ewma-2.0.1.tgz", @@ -59911,12 +59756,6 @@ } } }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", - "dev": true - }, "joi": { "version": "17.12.2", "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", @@ -65787,12 +65626,6 @@ "side-channel": "^1.0.4" } }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", - "dev": true - }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -66905,12 +66738,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "devOptional": true }, - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", - "dev": true - }, "scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -69458,24 +69285,6 @@ } } }, - "url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", - "dev": true - } - } - }, "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -70183,24 +69992,6 @@ "dev": true, "requires": {} }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - }, - "dependencies": { - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", - "dev": true - } - } - }, "xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/.tav.yml b/plugins/node/opentelemetry-instrumentation-aws-sdk/.tav.yml index 81c3388cf5..74e55d760f 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/.tav.yml +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/.tav.yml @@ -1,11 +1,3 @@ -"aws-sdk": - # A small subset of releases in the range [2.308.0, 3) to reduce testing time. - versions: - include: "^2.308.0" - mode: max-7 - commands: - - npm run test - # Versions [3.363.0, 3.377.0] of all @aws-sdk/client-* were bad releases. See: # - https://github.com/open-telemetry/opentelemetry-js-contrib/pull/2464#issuecomment-2403652552 # - https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1828#issuecomment-1834276719 diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/README.md b/plugins/node/opentelemetry-instrumentation-aws-sdk/README.md index ea51d907d4..dc288cfbb1 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/README.md +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/README.md @@ -5,7 +5,7 @@ [component owners](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/.github/component_owners.yml): @blumamir @jj22ee @trivikr -This module provides automatic instrumentation for the [`aws-sdk` v2](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/) and [`@aws-sdk` v3](https://github.com/aws/aws-sdk-js-v3) modules, which may be loaded using the [`@opentelemetry/sdk-trace-node`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) package and is included in the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle. +This module provides automatic instrumentation for the [AWS SDK for JavaScript v3](https://github.com/aws/aws-sdk-js-v3) modules, which may be loaded using the [`@opentelemetry/sdk-trace-node`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) package and is included in the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle. If total installation size is not constrained, it is recommended to use the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle with [@opentelemetry/sdk-node](`https://www.npmjs.com/package/@opentelemetry/sdk-node`) for the most seamless instrumentation experience. @@ -19,7 +19,6 @@ npm install --save @opentelemetry/instrumentation-aws-sdk ## Supported Versions -- [`aws-sdk`](https://www.npmjs.com/package/aws-sdk) versions `>=2.308.0 <3` - `@aws-sdk/client-*` versions `>=3.0.0 <4` ## Usage @@ -60,7 +59,7 @@ aws-sdk instrumentation has few options available to choose from. You can set th ## Span Attributes -Both V2 and V3 instrumentations are collecting the following attributes: +The instrumentations are collecting the following attributes: | Attribute Name | Type | Description | Example | | -------------- | ---- | ----------- | ------- | | `rpc.system` | string | Always equals "aws-api" | | @@ -68,21 +67,9 @@ Both V2 and V3 instrumentations are collecting the following attributes: | `rpc.service` | string | The name of the service to which a request is made, as returned by the AWS SDK. If the SDK does not provide a away to retrieve a name, the name of the SDK's client interface for a service SHOULD be used, removing the suffix `Client` if present, resulting in a PascalCase name with no spaces. | `S3`, `DynamoDB`, `Route53` | | `aws.region` | string | Region name for the request | "eu-west-1" | -### V2 attributes - -In addition to the above attributes, the instrumentation also collect the following for V2 ONLY: -| Attribute Name | Type | Description | Example | -| -------------- | ---- | ----------- | ------- | -| `aws.operation` | string | The method name for the request. | for `SQS.sendMessage(...)` the operation is "sendMessage" | -| `aws.signature.version` | string | AWS version of authentication signature on the request. | "v4" | -| `aws.service.api` | string | The SDK class name for the service | "SQS" | -| `aws.service.identifier` | string | Identifier for the service in the SDK | "sqs" | -| `aws.service.name` | string | Abbreviation name for the service | "Amazon SQS" | -| `aws.request.id` | uuid | Request unique id, as returned from aws on response | "01234567-89ab-cdef-0123-456789abcdef" | - ### Custom User Attributes -The instrumentation user can configure a `preRequestHook` function which will be called before each request, with a normalized request object (across v2 and v3) and the corresponding span. +The instrumentation user can configure a `preRequestHook` function which will be called before each request, with a normalized request object and the corresponding span. This hook can be used to add custom attributes to the span with any logic. For example, user can add interesting attributes from the `request.params`, and write custom logic based on the service and operation. Usage example: diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/package.json b/plugins/node/opentelemetry-instrumentation-aws-sdk/package.json index 25d7b2e8bb..b0541a1f1a 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/package.json +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/package.json @@ -64,7 +64,6 @@ "@types/mocha": "8.2.3", "@types/node": "18.18.14", "@types/sinon": "10.0.20", - "aws-sdk": "2.1008.0", "eslint": "8.7.0", "expect": "29.2.0", "nock": "13.3.3", diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts index a3040a15a9..8c9c62f126 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts @@ -18,12 +18,10 @@ import { SpanKind, context, trace, - Context, diag, SpanStatusCode, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import type * as AWS from 'aws-sdk'; import { AttributeNames } from './enums'; import { ServicesExtensions } from './services'; import { @@ -53,7 +51,6 @@ import type { import { bindPromise, extractAttributesFromNormalizedRequest, - normalizeV2Request, normalizeV3Request, removeSuffixFromStringIfExists, } from './utils'; @@ -68,11 +65,6 @@ type V3PluginCommand = AwsV3Command & { [V3_CLIENT_CONFIG_KEY]?: any; }; -const REQUEST_SPAN_KEY = Symbol('opentelemetry.instrumentation.aws-sdk.span'); -type V2PluginRequest = AWS.Request & { - [REQUEST_SPAN_KEY]?: Span; -}; - export class AwsInstrumentation extends InstrumentationBase { static readonly component = 'aws-sdk'; private servicesExtensions: ServicesExtensions = new ServicesExtensions(); @@ -141,23 +133,7 @@ export class AwsInstrumentation extends InstrumentationBase, - metadata: RequestMetadata, - normalizedRequest: NormalizedRequest - ): Span { - const operation = (request as any).operation; - const service = (request as any).service; - const serviceIdentifier = service?.serviceIdentifier; - const name = - metadata.spanName ?? - `${normalizedRequest.serviceName}.${normalizedRequest.commandName}`; - - const newSpan = this.tracer.startSpan(name, { - kind: metadata.spanKind ?? SpanKind.CLIENT, - attributes: { - [AttributeNames.AWS_OPERATION]: operation, - [AttributeNames.AWS_SIGNATURE_VERSION]: - service?.config?.signatureVersion, - [AttributeNames.AWS_SERVICE_API]: service?.api?.className, - [AttributeNames.AWS_SERVICE_IDENTIFIER]: serviceIdentifier, - [AttributeNames.AWS_SERVICE_NAME]: service?.api?.abbreviation, - ...extractAttributesFromNormalizedRequest(normalizedRequest), - ...metadata.spanAttributes, - }, - }); - - return newSpan; - } - private _callUserPreRequestHook( span: Span, request: NormalizedRequest, @@ -310,51 +232,6 @@ export class AwsInstrumentation extends InstrumentationBase { - // read issue https://github.com/aspecto-io/opentelemetry-ext-js/issues/60 - context.with(completedEventContext, () => { - if (!v2Request[REQUEST_SPAN_KEY]) { - return; - } - delete v2Request[REQUEST_SPAN_KEY]; - - const requestId = response.requestId; - const normalizedResponse: NormalizedResponse = { - data: response.data, - request: normalizedRequest, - requestId: requestId, - }; - - self._callUserResponseHook(span, normalizedResponse); - if (response.error) { - span.recordException(response.error); - } else { - this.servicesExtensions.responseHook( - normalizedResponse, - span, - self.tracer, - self.getConfig() - ); - } - - span.setAttribute(AttributeNames.AWS_REQUEST_ID, requestId); - - const httpStatusCode = response.httpResponse?.statusCode; - if (httpStatusCode) { - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, httpStatusCode); - } - span.end(); - }); - }); - } - private _getV3ConstructStackPatch( moduleVersion: string | undefined, original: (...args: unknown[]) => MiddlewareStack @@ -578,104 +455,6 @@ export class AwsInstrumentation extends InstrumentationBase void) => void - ) { - const self = this; - return function ( - this: V2PluginRequest, - callback?: (err: any, data: any) => void - ) { - /* - if the span was already started, we don't want to start a new one - when Request.promise() is called - */ - if (this[REQUEST_SPAN_KEY]) { - return original.call(this, callback); - } - - const normalizedRequest = normalizeV2Request(this); - const requestMetadata = self.servicesExtensions.requestPreSpanHook( - normalizedRequest, - self.getConfig(), - self._diag - ); - const span = self._startAwsV2Span( - this, - requestMetadata, - normalizedRequest - ); - this[REQUEST_SPAN_KEY] = span; - const activeContextWithSpan = trace.setSpan(context.active(), span); - const callbackWithContext = context.bind(activeContextWithSpan, callback); - - self._callUserPreRequestHook(span, normalizedRequest, moduleVersion); - self._registerV2CompletedEvent( - span, - this, - normalizedRequest, - activeContextWithSpan - ); - - return context.with(activeContextWithSpan, () => { - self.servicesExtensions.requestPostSpanHook(normalizedRequest); - return self._callOriginalFunction(() => - original.call(this, callbackWithContext) - ); - }); - }; - } - - private _getRequestPromisePatch( - moduleVersion: string | undefined, - original: (...args: unknown[]) => Promise - ) { - const self = this; - return function (this: V2PluginRequest, ...args: unknown[]): Promise { - // if the span was already started, we don't want to start a new one when Request.promise() is called - if (this[REQUEST_SPAN_KEY]) { - return original.apply(this, args); - } - - const normalizedRequest = normalizeV2Request(this); - const requestMetadata = self.servicesExtensions.requestPreSpanHook( - normalizedRequest, - self.getConfig(), - self._diag - ); - const span = self._startAwsV2Span( - this, - requestMetadata, - normalizedRequest - ); - this[REQUEST_SPAN_KEY] = span; - - const activeContextWithSpan = trace.setSpan(context.active(), span); - self._callUserPreRequestHook(span, normalizedRequest, moduleVersion); - self._registerV2CompletedEvent( - span, - this, - normalizedRequest, - activeContextWithSpan - ); - - const origPromise: Promise = context.with( - activeContextWithSpan, - () => { - self.servicesExtensions.requestPostSpanHook(normalizedRequest); - return self._callOriginalFunction(() => - original.call(this, arguments) - ); - } - ); - - return requestMetadata.isIncoming - ? bindPromise(origPromise, activeContextWithSpan) - : origPromise; - }; - } - private _callOriginalFunction(originalFunction: (...args: any[]) => T): T { if (this.getConfig().suppressInternalInstrumentation) { return context.with(suppressTracing(context.active()), originalFunction); diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/sqs.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/sqs.ts index 07cedaa25d..c64a2e4f0a 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/sqs.ts +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/sqs.ts @@ -25,7 +25,7 @@ import { } from '@opentelemetry/api'; import { pubsubPropagation } from '@opentelemetry/propagation-utils'; import { RequestMetadata, ServiceExtension } from './ServiceExtension'; -import type { SQS } from 'aws-sdk'; +import type { SQS } from '../aws-sdk.types'; import { AwsSdkInstrumentationConfig, NormalizedRequest, @@ -119,7 +119,7 @@ export class SqsServiceExtension implements ServiceExtension { const entries = request.commandInput?.Entries; if (Array.isArray(entries)) { entries.forEach( - (messageParams: SQS.SendMessageBatchRequestEntry) => { + (messageParams: { MessageAttributes: SQS.MessageBodyAttributeMap }) => { messageParams.MessageAttributes = injectPropagationContext( messageParams.MessageAttributes ?? {} ); diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/types.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/types.ts index ce99e8c441..03c30ca4e0 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/types.ts +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/types.ts @@ -20,7 +20,7 @@ import { SQS } from './aws-sdk.types'; export type CommandInput = Record; /** - * These are normalized request and response, which are used by both sdk v2 and v3. + * These are normalized request and response. * They organize the relevant data in one interface which can be processed in a * uniform manner in hooks */ diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/utils.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/utils.ts index cb8aaa6591..99295f0a06 100644 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/src/utils.ts +++ b/plugins/node/opentelemetry-instrumentation-aws-sdk/src/utils.ts @@ -22,9 +22,6 @@ import { import { AttributeNames } from './enums'; import { NormalizedRequest } from './types'; -const toPascalCase = (str: string): string => - typeof str === 'string' ? str.charAt(0).toUpperCase() + str.slice(1) : str; - export const removeSuffixFromStringIfExists = ( str: string, suffixToRemove: string @@ -35,16 +32,6 @@ export const removeSuffixFromStringIfExists = ( : str; }; -export const normalizeV2Request = (awsV2Request: any): NormalizedRequest => { - const service = awsV2Request?.service; - return { - serviceName: service?.api?.serviceId?.replace(/\s+/g, ''), - commandName: toPascalCase(awsV2Request?.operation), - commandInput: awsV2Request.params, - region: service?.config?.region, - }; -}; - export const normalizeV3Request = ( serviceName: string, commandNameWithSuffix: string, diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/aws-sdk-v2.test.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/test/aws-sdk-v2.test.ts deleted file mode 100644 index 545fe09bd0..0000000000 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/aws-sdk-v2.test.ts +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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. - */ -import { - AwsInstrumentation, - AwsSdkRequestHookInformation, - AwsSdkResponseHookInformation, -} from '../src'; -import { - getTestSpans, - registerInstrumentationTesting, -} from '@opentelemetry/contrib-test-utils'; -const instrumentation = registerInstrumentationTesting( - new AwsInstrumentation() -); -import * as AWS from 'aws-sdk'; - -import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { SpanStatusCode, Span, SpanKind } from '@opentelemetry/api'; -import { AttributeNames } from '../src/enums'; -import { mockV2AwsSend } from './testing-utils'; -import { expect } from 'expect'; -import { SEMATTRS_HTTP_STATUS_CODE } from '@opentelemetry/semantic-conventions'; -import { AWSError } from 'aws-sdk'; -import { HttpResponse } from 'aws-sdk/lib/http_response'; - -describe('instrumentation-aws-sdk-v2', () => { - const responseMockSuccess = { - requestId: '0000000000000', - error: null, - httpResponse: { - statusCode: 200, - }, - }; - - const error: AWSError = { - name: 'error', - message: 'something went wrong', - stack: 'fakeStack', - code: 'errorCode', - time: new Date(), - }; - - const responseMockWithError: Pick< - AWS.Response, - 'requestId' | 'error' - > & { httpResponse: Partial } = { - requestId: '0000000000000', - error, - httpResponse: { - statusCode: 400, - }, - }; - - const getAwsSpans = (): ReadableSpan[] => { - return getTestSpans().filter(s => - s.instrumentationLibrary.name.includes('aws-sdk') - ); - }; - - before(() => { - AWS.config.credentials = { - accessKeyId: 'test key id', - expired: false, - expireTime: new Date(), - secretAccessKey: 'test acc key', - sessionToken: 'test token', - }; - }); - - describe('functional', () => { - describe('successful send', () => { - before(() => { - mockV2AwsSend(responseMockSuccess); - }); - - it('adds proper number of spans with correct attributes', async () => { - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - const keyName = 'aws-test-object.txt'; - await new Promise(resolve => { - // span 1 - s3.createBucket({ Bucket: bucketName }, async (err, data) => { - const params = { - Bucket: bucketName, - Key: keyName, - Body: 'Hello World!', - }; - // span 2 - s3.putObject(params, (err, data) => { - if (err) console.log(err); - resolve({}); - }); - }); - }); - - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(2); - const [spanCreateBucket, spanPutObject] = awsSpans; - - expect(spanCreateBucket.attributes[AttributeNames.AWS_OPERATION]).toBe( - 'createBucket' - ); - expect( - spanCreateBucket.attributes[AttributeNames.AWS_SIGNATURE_VERSION] - ).toBe('s3'); - expect( - spanCreateBucket.attributes[AttributeNames.AWS_SERVICE_API] - ).toBe('S3'); - expect( - spanCreateBucket.attributes[AttributeNames.AWS_SERVICE_IDENTIFIER] - ).toBe('s3'); - expect( - spanCreateBucket.attributes[AttributeNames.AWS_SERVICE_NAME] - ).toBe('Amazon S3'); - expect(spanCreateBucket.attributes[AttributeNames.AWS_REQUEST_ID]).toBe( - responseMockSuccess.requestId - ); - expect(spanCreateBucket.attributes[AttributeNames.AWS_REGION]).toBe( - 'us-east-1' - ); - expect(spanCreateBucket.attributes[SEMATTRS_HTTP_STATUS_CODE]).toBe( - 200 - ); - - expect(spanCreateBucket.name).toBe('S3.CreateBucket'); - expect(spanCreateBucket.kind).toEqual(SpanKind.CLIENT); - expect(spanPutObject.attributes[AttributeNames.AWS_OPERATION]).toBe( - 'putObject' - ); - expect( - spanPutObject.attributes[AttributeNames.AWS_SIGNATURE_VERSION] - ).toBe('s3'); - expect(spanPutObject.attributes[AttributeNames.AWS_SERVICE_API]).toBe( - 'S3' - ); - expect( - spanPutObject.attributes[AttributeNames.AWS_SERVICE_IDENTIFIER] - ).toBe('s3'); - expect(spanPutObject.attributes[AttributeNames.AWS_SERVICE_NAME]).toBe( - 'Amazon S3' - ); - expect(spanPutObject.attributes[AttributeNames.AWS_REQUEST_ID]).toBe( - responseMockSuccess.requestId - ); - expect(spanPutObject.attributes[AttributeNames.AWS_REGION]).toBe( - 'us-east-1' - ); - expect(spanPutObject.name).toBe('S3.PutObject'); - expect(spanPutObject.attributes[SEMATTRS_HTTP_STATUS_CODE]).toBe(200); - }); - - it('adds proper number of spans with correct attributes if both, promise and callback were used', async () => { - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - const keyName = 'aws-test-object.txt'; - await new Promise(resolve => { - // span 1 - s3.createBucket({ Bucket: bucketName }, async (err, data) => { - const params = { - Bucket: bucketName, - Key: keyName, - Body: 'Hello World!', - }; - - let reqPromise: Promise | null = null; - let numberOfCalls = 0; - const cbPromise = new Promise(resolveCb => { - // span 2 - const request = s3.putObject(params, (err, data) => { - if (err) console.log(err); - numberOfCalls++; - if (numberOfCalls === 2) { - resolveCb({}); - } - }); - // NO span - reqPromise = request.promise(); - }); - - await Promise.all([cbPromise, reqPromise]).then(() => { - resolve({}); - }); - }); - }); - - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(2); - const [spanCreateBucket, spanPutObjectCb] = awsSpans; - expect(spanCreateBucket.attributes[AttributeNames.AWS_OPERATION]).toBe( - 'createBucket' - ); - expect(spanPutObjectCb.attributes[AttributeNames.AWS_OPERATION]).toBe( - 'putObject' - ); - expect(spanPutObjectCb.attributes[AttributeNames.AWS_REGION]).toBe( - 'us-east-1' - ); - expect(spanPutObjectCb.attributes[SEMATTRS_HTTP_STATUS_CODE]).toBe(200); - }); - - it('adds proper number of spans with correct attributes if only promise was used', async () => { - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - const keyName = 'aws-test-object.txt'; - await new Promise(resolve => { - // span 1 - s3.createBucket({ Bucket: bucketName }, async (err, data) => { - const params = { - Bucket: bucketName, - Key: keyName, - Body: 'Hello World!', - }; - - // NO span - const request = s3.putObject(params); - // span 2 - await request.promise(); - resolve({}); - }); - }); - - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(2); - const [spanCreateBucket, spanPutObjectCb] = awsSpans; - expect(spanCreateBucket.attributes[AttributeNames.AWS_OPERATION]).toBe( - 'createBucket' - ); - expect(spanPutObjectCb.attributes[AttributeNames.AWS_OPERATION]).toBe( - 'putObject' - ); - expect(spanPutObjectCb.attributes[AttributeNames.AWS_REGION]).toBe( - 'us-east-1' - ); - expect(spanPutObjectCb.attributes[SEMATTRS_HTTP_STATUS_CODE]).toBe(200); - }); - - it('should create span if no callback is supplied', done => { - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - - s3.putObject({ - Bucket: bucketName, - Key: 'key name from tests', - Body: 'Hello World!', - }).send(); - - setImmediate(() => { - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - done(); - }); - }); - }); - - describe('send return error', () => { - before(() => { - mockV2AwsSend(responseMockWithError); - }); - - it('adds error attribute properly', async () => { - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - await new Promise(resolve => { - s3.createBucket({ Bucket: bucketName }, async () => { - resolve({}); - }); - }); - - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - const [spanCreateBucket] = awsSpans; - const exceptionEvent = spanCreateBucket.events.filter( - event => event.name === 'exception' - ); - expect(exceptionEvent.length).toBe(1); - - expect(exceptionEvent[0]).toStrictEqual( - expect.objectContaining({ - name: 'exception', - attributes: { - 'exception.message': 'something went wrong', - 'exception.stacktrace': 'fakeStack', - 'exception.type': 'errorCode', - }, - }) - ); - - expect(spanCreateBucket.attributes[SEMATTRS_HTTP_STATUS_CODE]).toBe( - 400 - ); - }); - }); - }); - - describe('instrumentation config', () => { - it('preRequestHook called and add request attribute to span', done => { - mockV2AwsSend(responseMockSuccess, 'data returned from operation'); - const config = { - preRequestHook: ( - span: Span, - requestInfo: AwsSdkRequestHookInformation - ) => { - span.setAttribute( - 'attribute from hook', - requestInfo.request.commandInput['Bucket'] - ); - }, - }; - - instrumentation.disable(); - instrumentation.setConfig(config); - instrumentation.enable(); - - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - - s3.createBucket({ Bucket: bucketName }, async (err, data) => { - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - expect(awsSpans[0].attributes['attribute from hook']).toStrictEqual( - bucketName - ); - done(); - }); - }); - - it('preRequestHook throws does not fail span', done => { - mockV2AwsSend(responseMockSuccess, 'data returned from operation'); - const config = { - preRequestHook: (span: Span, request: any) => { - throw new Error('error from request hook'); - }, - }; - - instrumentation.disable(); - instrumentation.setConfig(config); - instrumentation.enable(); - - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - - s3.createBucket({ Bucket: bucketName }, async (err, data) => { - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - expect(awsSpans[0].status.code).toStrictEqual(SpanStatusCode.UNSET); - done(); - }); - }); - - it('responseHook called and add response attribute to span', done => { - mockV2AwsSend(responseMockSuccess, 'data returned from operation'); - const config = { - responseHook: ( - span: Span, - responseInfo: AwsSdkResponseHookInformation - ) => { - span.setAttribute( - 'attribute from response hook', - responseInfo.response['data'] - ); - }, - }; - - instrumentation.disable(); - instrumentation.setConfig(config); - instrumentation.enable(); - - const s3 = new AWS.S3(); - const bucketName = 'aws-test-bucket'; - - s3.createBucket({ Bucket: bucketName }, async (err, data) => { - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - expect( - awsSpans[0].attributes['attribute from response hook'] - ).toStrictEqual('data returned from operation'); - done(); - }); - }); - - it('suppressInternalInstrumentation set to true with send()', done => { - mockV2AwsSend(responseMockSuccess, 'data returned from operation', true); - const config = { - suppressInternalInstrumentation: true, - }; - - instrumentation.disable(); - instrumentation.setConfig(config); - instrumentation.enable(); - - const s3 = new AWS.S3(); - - s3.createBucket({ Bucket: 'aws-test-bucket' }, (err, data) => { - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - done(); - }); - }); - - it('suppressInternalInstrumentation set to true with promise()', async () => { - mockV2AwsSend(responseMockSuccess, 'data returned from operation', true); - const config = { - suppressInternalInstrumentation: true, - }; - - instrumentation.disable(); - instrumentation.setConfig(config); - instrumentation.enable(); - - const s3 = new AWS.S3(); - - await s3.createBucket({ Bucket: 'aws-test-bucket' }).promise(); - const awsSpans = getAwsSpans(); - expect(awsSpans.length).toBe(1); - }); - }); -}); diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/dynamodb.test.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/test/dynamodb.test.ts deleted file mode 100644 index de34fd8a84..0000000000 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/dynamodb.test.ts +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * 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. - */ -import { - AwsInstrumentation, - CommandInput, - AwsSdkDynamoDBStatementSerializer, -} from '../src'; -import { - getTestSpans, - registerInstrumentationTesting, -} from '@opentelemetry/contrib-test-utils'; -const instrumentation = registerInstrumentationTesting( - new AwsInstrumentation() -); -import * as AWS from 'aws-sdk'; -import { AWSError } from 'aws-sdk'; - -import { mockV2AwsSend } from './testing-utils'; -import { - DBSYSTEMVALUES_DYNAMODB, - SEMATTRS_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS, - SEMATTRS_AWS_DYNAMODB_CONSISTENT_READ, - SEMATTRS_AWS_DYNAMODB_CONSUMED_CAPACITY, - SEMATTRS_AWS_DYNAMODB_COUNT, - SEMATTRS_AWS_DYNAMODB_EXCLUSIVE_START_TABLE, - SEMATTRS_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES, - SEMATTRS_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES, - SEMATTRS_AWS_DYNAMODB_INDEX_NAME, - SEMATTRS_AWS_DYNAMODB_ITEM_COLLECTION_METRICS, - SEMATTRS_AWS_DYNAMODB_LIMIT, - SEMATTRS_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES, - SEMATTRS_AWS_DYNAMODB_PROJECTION, - SEMATTRS_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY, - SEMATTRS_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY, - SEMATTRS_AWS_DYNAMODB_SCAN_FORWARD, - SEMATTRS_AWS_DYNAMODB_SCANNED_COUNT, - SEMATTRS_AWS_DYNAMODB_SEGMENT, - SEMATTRS_AWS_DYNAMODB_SELECT, - SEMATTRS_AWS_DYNAMODB_TABLE_COUNT, - SEMATTRS_AWS_DYNAMODB_TABLE_NAMES, - SEMATTRS_AWS_DYNAMODB_TOTAL_SEGMENTS, - SEMATTRS_DB_NAME, - SEMATTRS_DB_OPERATION, - SEMATTRS_DB_STATEMENT, - SEMATTRS_DB_SYSTEM, -} from '@opentelemetry/semantic-conventions'; -import { expect } from 'expect'; -import type { ConsumedCapacity as ConsumedCapacityV2 } from 'aws-sdk/clients/dynamodb'; -import type { ConsumedCapacity as ConsumedCapacityV3 } from '@aws-sdk/client-dynamodb'; -import * as sinon from 'sinon'; - -type ConsumedCapacity = ConsumedCapacityV2 | ConsumedCapacityV3; - -const responseMockSuccess = { - requestId: '0000000000000', - error: null, -}; - -describe('DynamoDB', () => { - before(() => { - AWS.config.credentials = { - accessKeyId: 'test key id', - expired: false, - expireTime: new Date(), - secretAccessKey: 'test acc key', - sessionToken: 'test token', - }; - }); - - describe('Query', () => { - beforeEach(() => { - mockV2AwsSend(responseMockSuccess, { - Items: [{ key1: 'val1' }, { key2: 'val2' }], - Count: 2, - ScannedCount: 5, - } as AWS.DynamoDB.Types.QueryOutput); - }); - - it('should populate specific Query attributes', done => { - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - KeyConditionExpression: '#k = :v', - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - ProjectionExpression: 'id', - ScanIndexForward: true, - ConsistentRead: true, - IndexName: 'name_to_group', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.query( - params, - (err: AWSError, data: AWS.DynamoDB.DocumentClient.QueryOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect(attrs[SEMATTRS_DB_NAME]).toStrictEqual('test-table'); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('Query'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_SCAN_FORWARD]).toStrictEqual(true); - expect(attrs[SEMATTRS_AWS_DYNAMODB_CONSISTENT_READ]).toStrictEqual( - true - ); - expect(attrs[SEMATTRS_AWS_DYNAMODB_INDEX_NAME]).toStrictEqual( - 'name_to_group' - ); - expect(attrs[SEMATTRS_AWS_DYNAMODB_SELECT]).toStrictEqual( - 'ALL_ATTRIBUTES' - ); - expect(attrs[SEMATTRS_AWS_DYNAMODB_LIMIT]).toStrictEqual(10); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TABLE_NAMES]).toStrictEqual([ - 'test-table', - ]); - expect(attrs[SEMATTRS_AWS_DYNAMODB_PROJECTION]).toStrictEqual('id'); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('Scan', () => { - beforeEach(() => - mockV2AwsSend(responseMockSuccess, { - ConsumedCapacity: { - TableName: 'test-table', - CapacityUnits: 0.5, - Table: { CapacityUnits: 0.5 }, - }, - Count: 10, - ScannedCount: 50, - } as AWS.DynamoDB.Types.ScanOutput) - ); - - it('should populate specific Scan attributes', done => { - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - Item: { key1: 'val1' }, - ProjectionExpression: 'id', - ConsistentRead: true, - Segment: 10, - TotalSegments: 100, - IndexName: 'index_name', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.scan( - params, - (err: AWSError, data: AWS.DynamoDB.DocumentClient.ScanOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect(attrs[SEMATTRS_DB_NAME]).toStrictEqual('test-table'); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('Scan'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_SEGMENT]).toStrictEqual(10); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TOTAL_SEGMENTS]).toStrictEqual( - 100 - ); - expect(attrs[SEMATTRS_AWS_DYNAMODB_INDEX_NAME]).toStrictEqual( - 'index_name' - ); - expect(attrs[SEMATTRS_AWS_DYNAMODB_SELECT]).toStrictEqual( - 'ALL_ATTRIBUTES' - ); - expect(attrs[SEMATTRS_AWS_DYNAMODB_COUNT]).toStrictEqual(10); - expect(attrs[SEMATTRS_AWS_DYNAMODB_SCANNED_COUNT]).toStrictEqual(50); - expect(attrs[SEMATTRS_AWS_DYNAMODB_LIMIT]).toStrictEqual(10); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TABLE_NAMES]).toStrictEqual([ - 'test-table', - ]); - expect(attrs[SEMATTRS_AWS_DYNAMODB_PROJECTION]).toStrictEqual('id'); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('BatchWriteItem', () => { - beforeEach(() => - mockV2AwsSend(responseMockSuccess, { - UnprocessedItems: {}, - ItemCollectionMetrics: { - ItemCollectionKey: [], - SizeEstimateRangeGB: [0], - }, - ConsumedCapacity: undefined, - } as AWS.DynamoDB.Types.BatchWriteItemOutput) - ); - - it('should populate specific BatchWriteItem attributes', done => { - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - RequestItems: {}, - ReturnConsumedCapacity: 'INDEXES', - ReturnItemCollectionMetrics: 'SIZE', - }; - - dynamodb.batchWrite( - params, - ( - err: AWSError, - data: AWS.DynamoDB.DocumentClient.BatchWriteItemOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_ITEM_COLLECTION_METRICS] - ).toStrictEqual([ - JSON.stringify({ ItemCollectionKey: [], SizeEstimateRangeGB: [0] }), - ]); - - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('CreateTable', () => { - beforeEach(() => - mockV2AwsSend(responseMockSuccess, { - TableName: 'test_table', - ItemCollectionMetrics: { - ItemCollectionKey: [], - SizeEstimateRangeGB: [0], - }, - ConsumedCapacity: undefined, - } as AWS.DynamoDB.Types.CreateTableOutput) - ); - - it('should populate specific CreateTable attributes', done => { - const globalSecondaryIndexMockData = { - IndexName: 'test_index', - KeySchema: [ - { - AttributeName: 'attribute1', - KeyType: 'HASH', - }, - ], - Projection: { - ProjectionType: 'ALL', - NonKeyAttributes: ['non_key_attr'], - }, - ProvisionedThroughput: { - ReadCapacityUnits: 5, - WriteCapacityUnits: 10, - }, - }; - - const localSecondaryIndexMockData = { - IndexName: 'test_index', - KeySchema: [ - { - AttributeName: 'test_attribute', - KeyType: 'HASH', - }, - ], - Projection: { - ProjectionType: 'ALL', - NonKeyAttributes: ['STRING_VALUE'], - }, - }; - - const dynamodb = new AWS.DynamoDB(); - const params = { - AttributeDefinitions: [ - { - AttributeName: 'test_attribute', - AttributeType: 'S', - }, - ], - TableName: 'test_table', - KeySchema: [ - { - AttributeName: 'test_attribute', - KeyType: 'HASH', - }, - ], - LocalSecondaryIndexes: [localSecondaryIndexMockData], - GlobalSecondaryIndexes: [globalSecondaryIndexMockData], - BillingMode: 'PROVISIONED', - ProvisionedThroughput: { - ReadCapacityUnits: 20, - WriteCapacityUnits: 30, - }, - }; - - dynamodb.createTable( - params, - ( - err: AWSError, - data: AWS.DynamoDB.DocumentClient.CreateTableOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_ITEM_COLLECTION_METRICS] - ).toStrictEqual([ - JSON.stringify({ ItemCollectionKey: [], SizeEstimateRangeGB: [0] }), - ]); - - expect( - attrs[SEMATTRS_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES] - ).toStrictEqual([JSON.stringify(globalSecondaryIndexMockData)]); - - expect( - attrs[SEMATTRS_AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES] - ).toStrictEqual([JSON.stringify(localSecondaryIndexMockData)]); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY] - ).toStrictEqual(20); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY] - ).toStrictEqual(30); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('UpdateTable', () => { - beforeEach(() => - mockV2AwsSend(responseMockSuccess, { - TableName: 'test_table', - } as AWS.DynamoDB.Types.UpdateTableOutput) - ); - - it('should populate specific CreateTable attributes', done => { - const dynamodb = new AWS.DynamoDB(); - const params = { - AttributeDefinitions: [ - { - AttributeName: 'test_attr', - AttributeType: 'S', - }, - ], - TableName: 'test_table', - ProvisionedThroughput: { - ReadCapacityUnits: 10, - WriteCapacityUnits: 15, - }, - GlobalSecondaryIndexUpdates: [ - { - Update: { - IndexName: 'test_index', - ProvisionedThroughput: { - ReadCapacityUnits: 1, - WriteCapacityUnits: 5, - }, - }, - }, - ], - }; - - dynamodb.updateTable( - params, - ( - err: AWSError, - data: AWS.DynamoDB.DocumentClient.UpdateTableOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - - expect( - attrs[SEMATTRS_AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES] - ).toStrictEqual([ - JSON.stringify({ - Update: { - IndexName: 'test_index', - ProvisionedThroughput: { - ReadCapacityUnits: 1, - WriteCapacityUnits: 5, - }, - }, - }), - ]); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS] - ).toStrictEqual([ - JSON.stringify({ - AttributeName: 'test_attr', - AttributeType: 'S', - }), - ]); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_PROVISIONED_READ_CAPACITY] - ).toStrictEqual(10); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY] - ).toStrictEqual(15); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('ListTables', () => { - beforeEach(() => - mockV2AwsSend(responseMockSuccess, { - TableNames: ['test_table', 'test_table_2', 'start_table'], - } as AWS.DynamoDB.Types.ListTablesOutput) - ); - - it('should populate specific ListTables attributes', done => { - const dynamodb = new AWS.DynamoDB(); - const params = { - ExclusiveStartTableName: 'start_table', - Limit: 10, - }; - - dynamodb.listTables( - params, - (err: AWSError, data: AWS.DynamoDB.DocumentClient.ListTablesOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - - expect( - attrs[SEMATTRS_AWS_DYNAMODB_EXCLUSIVE_START_TABLE] - ).toStrictEqual('start_table'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_LIMIT]).toStrictEqual(10); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TABLE_COUNT]).toStrictEqual(3); - - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('BatchGetItem', () => { - const consumedCapacityResponseMockData: ConsumedCapacity[] = [ - { - TableName: 'test-table', - CapacityUnits: 0.5, - Table: { CapacityUnits: 0.5 }, - }, - ]; - - it('should populate BatchGetIem default attributes', done => { - mockV2AwsSend(responseMockSuccess, { - Responses: { 'test-table': [{ key1: { S: 'val1' } }] }, - UnprocessedKeys: {}, - } as AWS.DynamoDB.Types.BatchGetItemOutput); - - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const dynamodb_params = { - RequestItems: { - 'test-table': { - Keys: [{ key1: { S: 'val1' } }], - ProjectionExpression: 'id', - }, - }, - ReturnConsumedCapacity: 'INDEXES', - }; - dynamodb.batchGet( - dynamodb_params, - ( - err: AWSError, - data: AWS.DynamoDB.DocumentClient.BatchGetItemOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('BatchGetItem'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TABLE_NAMES]).toStrictEqual([ - 'test-table', - ]); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_CONSUMED_CAPACITY] - ).toBeUndefined(); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should populate BatchGetIem optional attributes', done => { - mockV2AwsSend(responseMockSuccess, { - Responses: { 'test-table': [{ key1: { S: 'val1' } }] }, - UnprocessedKeys: {}, - ConsumedCapacity: consumedCapacityResponseMockData, - } as AWS.DynamoDB.Types.BatchGetItemOutput); - - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const dynamodb_params = { - RequestItems: { - 'test-table': { - Keys: [{ key1: { S: 'val1' } }], - ProjectionExpression: 'id', - }, - }, - ReturnConsumedCapacity: 'INDEXES', - }; - dynamodb.batchGet( - dynamodb_params, - ( - err: AWSError, - data: AWS.DynamoDB.DocumentClient.BatchGetItemOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('BatchGetItem'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TABLE_NAMES]).toStrictEqual([ - 'test-table', - ]); - expect(attrs[SEMATTRS_AWS_DYNAMODB_CONSUMED_CAPACITY]).toStrictEqual( - consumedCapacityResponseMockData.map((x: ConsumedCapacity) => - JSON.stringify(x) - ) - ); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should populate BatchGetIem when consumedCapacity is undefined', done => { - mockV2AwsSend(responseMockSuccess, { - Responses: { 'test-table': [{ key1: { S: 'val1' } }] }, - UnprocessedKeys: {}, - ConsumedCapacity: undefined, - } as AWS.DynamoDB.Types.BatchGetItemOutput); - - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const dynamodb_params = { - RequestItems: { - 'test-table': { - Keys: [{ key1: { S: 'val1' } }], - ProjectionExpression: 'id', - }, - }, - ReturnConsumedCapacity: 'NONE', - }; - dynamodb.batchGet( - dynamodb_params, - ( - err: AWSError, - data: AWS.DynamoDB.DocumentClient.BatchGetItemOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual('dynamodb'); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('BatchGetItem'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_TABLE_NAMES]).toStrictEqual([ - 'test-table', - ]); - expect( - attrs[SEMATTRS_AWS_DYNAMODB_CONSUMED_CAPACITY] - ).toBeUndefined(); - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('ConsumedCapacity', () => { - it('should populate ConsumedCapacity attributes when they exist', done => { - mockV2AwsSend(responseMockSuccess, { - ConsumedCapacity: { - TableName: 'test-table', - CapacityUnits: 0.5, - Table: { CapacityUnits: 0.5 }, - }, - } as AWS.DynamoDB.Types.PutItemOutput); - - const dynamodb = new AWS.DynamoDB.DocumentClient(); - dynamodb.put( - { - TableName: 'test-table', - Item: { key1: 'val1' }, - ReturnConsumedCapacity: 'INDEXES', - }, - (err: AWSError, data: AWS.DynamoDB.DocumentClient.PutItemOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('PutItem'); - expect(attrs[SEMATTRS_AWS_DYNAMODB_CONSUMED_CAPACITY]).toStrictEqual([ - JSON.stringify({ - TableName: 'test-table', - CapacityUnits: 0.5, - Table: { CapacityUnits: 0.5 }, - }), - ]); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should not populate ConsumedCapacity attributes when it is not returned', done => { - mockV2AwsSend(responseMockSuccess, { - ConsumedCapacity: undefined, - } as AWS.DynamoDB.Types.PutItemOutput); - - const dynamodb = new AWS.DynamoDB.DocumentClient(); - dynamodb.put( - { - TableName: 'test-table', - Item: { key1: 'val1' }, - ReturnConsumedCapacity: 'NONE', - }, - (err: AWSError, data: AWS.DynamoDB.DocumentClient.PutItemOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - expect(attrs[SEMATTRS_DB_SYSTEM]).toStrictEqual( - DBSYSTEMVALUES_DYNAMODB - ); - expect(attrs[SEMATTRS_DB_OPERATION]).toStrictEqual('PutItem'); - expect(attrs).not.toHaveProperty( - SEMATTRS_AWS_DYNAMODB_CONSUMED_CAPACITY - ); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); - - describe('dbStatementSerializer config', () => { - const SERIALIZED_DB_STATEMENT = 'serialized statement'; - - const dynamoDBStatementSerializer: AwsSdkDynamoDBStatementSerializer = ( - _operation: string, - _command: CommandInput - ): string => { - return SERIALIZED_DB_STATEMENT; - }; - - beforeEach(() => { - instrumentation.disable(); - instrumentation.setConfig({ - dynamoDBStatementSerializer, - }); - instrumentation.enable(); - }); - - it('should not fail if serializer throws', done => { - instrumentation.disable(); - instrumentation.setConfig({ - dynamoDBStatementSerializer: () => { - throw new Error('Serializer failure'); - }, - }); - instrumentation.enable(); - mockV2AwsSend(responseMockSuccess, { - Items: [{ key1: 'val1' }, { key2: 'val2' }], - Count: 2, - ScannedCount: 5, - } as AWS.DynamoDB.Types.QueryOutput); - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - KeyConditionExpression: '#k = :v', - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - ProjectionExpression: 'id', - ScanIndexForward: true, - ConsistentRead: true, - IndexName: 'name_to_group', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.query( - params, - (err: AWSError, _data: AWS.DynamoDB.DocumentClient.QueryOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should omit DB statement if serializer is not configured', done => { - instrumentation.disable(); - instrumentation.setConfig({ - dynamoDBStatementSerializer: undefined, - }); - instrumentation.enable(); - mockV2AwsSend(responseMockSuccess, { - Items: [{ key1: 'val1' }, { key2: 'val2' }], - Count: 2, - ScannedCount: 5, - } as AWS.DynamoDB.Types.QueryOutput); - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - KeyConditionExpression: '#k = :v', - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - ProjectionExpression: 'id', - ScanIndexForward: true, - ConsistentRead: true, - IndexName: 'name_to_group', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.query( - params, - (err: AWSError, _data: AWS.DynamoDB.DocumentClient.QueryOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should omit DB statement if serializer returns undefined', done => { - instrumentation.disable(); - instrumentation.setConfig({ - dynamoDBStatementSerializer: () => undefined, - }); - instrumentation.enable(); - mockV2AwsSend(responseMockSuccess, { - Items: [{ key1: 'val1' }, { key2: 'val2' }], - Count: 2, - ScannedCount: 5, - } as AWS.DynamoDB.Types.QueryOutput); - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - KeyConditionExpression: '#k = :v', - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - ProjectionExpression: 'id', - ScanIndexForward: true, - ConsistentRead: true, - IndexName: 'name_to_group', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.query( - params, - (err: AWSError, _data: AWS.DynamoDB.DocumentClient.QueryOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs).not.toHaveProperty(SEMATTRS_DB_STATEMENT); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should provide operation and command input to serializer', done => { - const dynamoDBStatementSerializerSpy = sinon.spy(); - instrumentation.disable(); - instrumentation.setConfig({ - dynamoDBStatementSerializer: dynamoDBStatementSerializerSpy, - }); - instrumentation.enable(); - mockV2AwsSend(responseMockSuccess, { - Items: [{ key1: 'val1' }, { key2: 'val2' }], - Count: 2, - ScannedCount: 5, - } as AWS.DynamoDB.Types.QueryOutput); - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - KeyConditionExpression: '#k = :v', - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - ProjectionExpression: 'id', - ScanIndexForward: true, - ConsistentRead: true, - IndexName: 'name_to_group', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.query( - params, - (err: AWSError, _data: AWS.DynamoDB.DocumentClient.QueryOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - - expect(dynamoDBStatementSerializerSpy.callCount).toBe(1); - expect(dynamoDBStatementSerializerSpy.args[0][0]).toStrictEqual( - 'Query' - ); - expect(dynamoDBStatementSerializerSpy.args[0][1]).toStrictEqual({ - ConsistentRead: true, - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - IndexName: 'name_to_group', - KeyConditionExpression: '#k = :v', - Limit: 10, - ProjectionExpression: 'id', - ScanIndexForward: true, - Select: 'ALL_ATTRIBUTES', - TableName: 'test-table', - }); - - done(); - } - ); - }); - - it('should properly execute the db statement serializer for Query operation', done => { - mockV2AwsSend(responseMockSuccess, { - Items: [{ key1: 'val1' }, { key2: 'val2' }], - Count: 2, - ScannedCount: 5, - } as AWS.DynamoDB.Types.QueryOutput); - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - TableName: 'test-table', - KeyConditionExpression: '#k = :v', - ExpressionAttributeNames: { - '#k': 'key1', - }, - ExpressionAttributeValues: { - ':v': 'val1', - }, - ProjectionExpression: 'id', - ScanIndexForward: true, - ConsistentRead: true, - IndexName: 'name_to_group', - Limit: 10, - Select: 'ALL_ATTRIBUTES', - }; - - dynamodb.query( - params, - (err: AWSError, _data: AWS.DynamoDB.DocumentClient.QueryOutput) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs[SEMATTRS_DB_STATEMENT]).toStrictEqual( - SERIALIZED_DB_STATEMENT - ); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should properly execute the db statement serializer for CreateTable operation', done => { - mockV2AwsSend(responseMockSuccess, { - TableName: 'test_table', - ItemCollectionMetrics: { - ItemCollectionKey: [], - SizeEstimateRangeGB: [0], - }, - ConsumedCapacity: undefined, - } as AWS.DynamoDB.Types.CreateTableOutput); - const globalSecondaryIndexMockData = { - IndexName: 'test_index', - KeySchema: [ - { - AttributeName: 'attribute1', - KeyType: 'HASH', - }, - ], - Projection: { - ProjectionType: 'ALL', - NonKeyAttributes: ['non_key_attr'], - }, - ProvisionedThroughput: { - ReadCapacityUnits: 5, - WriteCapacityUnits: 10, - }, - }; - - const localSecondaryIndexMockData = { - IndexName: 'test_index', - KeySchema: [ - { - AttributeName: 'test_attribute', - KeyType: 'HASH', - }, - ], - Projection: { - ProjectionType: 'ALL', - NonKeyAttributes: ['STRING_VALUE'], - }, - }; - - const dynamodb = new AWS.DynamoDB(); - const params = { - AttributeDefinitions: [ - { - AttributeName: 'test_attribute', - AttributeType: 'S', - }, - ], - TableName: 'test_table', - KeySchema: [ - { - AttributeName: 'test_attribute', - KeyType: 'HASH', - }, - ], - LocalSecondaryIndexes: [localSecondaryIndexMockData], - GlobalSecondaryIndexes: [globalSecondaryIndexMockData], - BillingMode: 'PROVISIONED', - ProvisionedThroughput: { - ReadCapacityUnits: 20, - WriteCapacityUnits: 30, - }, - }; - - dynamodb.createTable( - params, - ( - err: AWSError, - _data: AWS.DynamoDB.DocumentClient.CreateTableOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs[SEMATTRS_DB_STATEMENT]).toStrictEqual( - SERIALIZED_DB_STATEMENT - ); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should properly execute the db statement serializer for UpdateTable operation', done => { - mockV2AwsSend(responseMockSuccess, { - TableName: 'test_table', - } as AWS.DynamoDB.Types.UpdateTableOutput); - const dynamodb = new AWS.DynamoDB(); - const params = { - AttributeDefinitions: [ - { - AttributeName: 'test_attr', - AttributeType: 'S', - }, - ], - TableName: 'test_table', - ProvisionedThroughput: { - ReadCapacityUnits: 10, - WriteCapacityUnits: 15, - }, - GlobalSecondaryIndexUpdates: [ - { - Update: { - IndexName: 'test_index', - ProvisionedThroughput: { - ReadCapacityUnits: 1, - WriteCapacityUnits: 5, - }, - }, - }, - ], - }; - - dynamodb.updateTable( - params, - ( - err: AWSError, - _data: AWS.DynamoDB.DocumentClient.UpdateTableOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs[SEMATTRS_DB_STATEMENT]).toStrictEqual( - SERIALIZED_DB_STATEMENT - ); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should properly execute the db statement serializer for ListTables operation', done => { - mockV2AwsSend(responseMockSuccess, { - TableNames: ['test_table', 'test_table_2', 'start_table'], - } as AWS.DynamoDB.Types.ListTablesOutput); - const dynamodb = new AWS.DynamoDB(); - const params = { - ExclusiveStartTableName: 'start_table', - Limit: 10, - }; - - dynamodb.listTables( - params, - ( - err: AWSError, - _data: AWS.DynamoDB.DocumentClient.ListTablesOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs[SEMATTRS_DB_STATEMENT]).toStrictEqual( - SERIALIZED_DB_STATEMENT - ); - expect(err).toBeFalsy(); - done(); - } - ); - }); - - it('should properly execute the db statement serializer for BatchWriteItem operation', done => { - mockV2AwsSend(responseMockSuccess, { - UnprocessedItems: {}, - ItemCollectionMetrics: { - ItemCollectionKey: [], - SizeEstimateRangeGB: [0], - }, - ConsumedCapacity: undefined, - } as AWS.DynamoDB.Types.BatchWriteItemOutput); - const dynamodb = new AWS.DynamoDB.DocumentClient(); - const params = { - RequestItems: {}, - ReturnConsumedCapacity: 'INDEXES', - ReturnItemCollectionMetrics: 'SIZE', - }; - - dynamodb.batchWrite( - params, - ( - err: AWSError, - _data: AWS.DynamoDB.DocumentClient.BatchWriteItemOutput - ) => { - const spans = getTestSpans(); - expect(spans.length).toStrictEqual(1); - const attrs = spans[0].attributes; - - expect(attrs[SEMATTRS_DB_STATEMENT]).toStrictEqual( - SERIALIZED_DB_STATEMENT - ); - expect(err).toBeFalsy(); - done(); - } - ); - }); - }); -}); diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/sns.test.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/test/sns.test.ts deleted file mode 100644 index f8d4dd777f..0000000000 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/sns.test.ts +++ /dev/null @@ -1,239 +0,0 @@ -/* - * 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. - */ -import { AwsInstrumentation } from '../src'; -import { - getTestSpans, - registerInstrumentationTesting, -} from '@opentelemetry/contrib-test-utils'; -const instrumentation = registerInstrumentationTesting( - new AwsInstrumentation() -); -import * as AWSv2 from 'aws-sdk'; -import { SNS as SNSv3 } from '@aws-sdk/client-sns'; -import * as fs from 'fs'; -import * as nock from 'nock'; - -import { mockV2AwsSend } from './testing-utils'; -import { expect } from 'expect'; -import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import * as sinon from 'sinon'; -import { - MESSAGINGDESTINATIONKINDVALUES_TOPIC, - SEMATTRS_MESSAGING_DESTINATION, - SEMATTRS_MESSAGING_DESTINATION_KIND, - SEMATTRS_MESSAGING_SYSTEM, - SEMATTRS_RPC_METHOD, -} from '@opentelemetry/semantic-conventions'; -import { SpanKind } from '@opentelemetry/api'; - -const responseMockSuccess = { - requestId: '0000000000000', - error: null, -}; - -const topicName = 'topic'; -const fakeARN = `arn:aws:sns:region:000000000:${topicName}`; - -describe('SNS - v2', () => { - before(() => { - AWSv2.config.credentials = { - accessKeyId: 'test key id', - expired: false, - expireTime: new Date(), - secretAccessKey: 'test acc key', - sessionToken: 'test token', - }; - }); - - beforeEach(() => { - mockV2AwsSend(responseMockSuccess, { - MessageId: '1', - } as AWS.SNS.Types.PublishResponse); - }); - - describe('publish', () => { - it('topic arn', async () => { - const sns = new AWSv2.SNS(); - - await sns - .publish({ - Message: 'sns message', - TopicArn: fakeARN, - }) - .promise(); - - const publishSpans = getTestSpans().filter( - (s: ReadableSpan) => s.name === `${topicName} send` - ); - expect(publishSpans.length).toBe(1); - - const publishSpan = publishSpans[0]; - expect(publishSpan.attributes[SEMATTRS_MESSAGING_DESTINATION_KIND]).toBe( - MESSAGINGDESTINATIONKINDVALUES_TOPIC - ); - expect(publishSpan.attributes[SEMATTRS_MESSAGING_DESTINATION]).toBe( - topicName - ); - expect(publishSpan.attributes['messaging.destination.name']).toBe( - fakeARN - ); - expect(publishSpan.attributes[SEMATTRS_RPC_METHOD]).toBe('Publish'); - expect(publishSpan.attributes[SEMATTRS_MESSAGING_SYSTEM]).toBe('aws.sns'); - expect(publishSpan.kind).toBe(SpanKind.PRODUCER); - }); - - it('phone number', async () => { - const sns = new AWSv2.SNS(); - const PhoneNumber = 'my phone number'; - await sns - .publish({ - Message: 'sns message', - PhoneNumber, - }) - .promise(); - - const publishSpans = getTestSpans().filter( - (s: ReadableSpan) => s.name === 'phone_number send' - ); - expect(publishSpans.length).toBe(1); - const publishSpan = publishSpans[0]; - expect(publishSpan.attributes[SEMATTRS_MESSAGING_DESTINATION]).toBe( - PhoneNumber - ); - expect(publishSpan.attributes['messaging.destination.name']).toBe( - PhoneNumber - ); - }); - - it('inject context propagation', async () => { - const sns = new AWSv2.SNS(); - const hookSpy = sinon.spy( - (instrumentation['servicesExtensions'] as any)['services'].get('SNS'), - 'requestPostSpanHook' - ); - - await sns - .publish({ - Message: 'sns message', - TopicArn: fakeARN, - }) - .promise(); - - const publishSpans = getTestSpans().filter( - (s: ReadableSpan) => s.name === `${topicName} send` - ); - expect(publishSpans.length).toBe(1); - expect( - hookSpy.args[0][0].commandInput.MessageAttributes.traceparent - ).toBeDefined(); - }); - }); - - describe('createTopic', () => { - it('basic createTopic creates a valid span', async () => { - const sns = new AWSv2.SNS(); - - const Name = 'my new topic'; - await sns.createTopic({ Name }).promise(); - - const spans = getTestSpans(); - const createTopicSpans = spans.filter( - (s: ReadableSpan) => s.name === 'SNS CreateTopic' - ); - expect(createTopicSpans.length).toBe(1); - - const createTopicSpan = createTopicSpans[0]; - expect( - createTopicSpan.attributes[SEMATTRS_MESSAGING_DESTINATION_KIND] - ).toBeUndefined(); - expect( - createTopicSpan.attributes[SEMATTRS_MESSAGING_DESTINATION] - ).toBeUndefined(); - expect( - createTopicSpan.attributes['messaging.destination.name'] - ).toBeUndefined(); - expect(createTopicSpan.kind).toBe(SpanKind.CLIENT); - }); - }); -}); - -describe('SNS - v3', () => { - let sns: any; - beforeEach(() => { - sns = new SNSv3({ - region: 'us-east-1', - credentials: { - accessKeyId: 'abcde', - secretAccessKey: 'abcde', - }, - }); - - nock('https://sns.us-east-1.amazonaws.com/') - .post('/') - .reply( - 200, - fs.readFileSync('./test/mock-responses/sns-publish.xml', 'utf8') - ); - }); - - describe('publish', () => { - it('topic arn', async () => { - const topicV3Name = 'dummy-sns-v3-topic'; - const topicV3ARN = `arn:aws:sns:us-east-1:000000000:${topicV3Name}`; - - await sns.publish({ - Message: 'sns message', - TopicArn: topicV3ARN, - }); - - const publishSpans = getTestSpans().filter( - (s: ReadableSpan) => s.name === `${topicV3Name} send` - ); - expect(publishSpans.length).toBe(1); - - const publishSpan = publishSpans[0]; - expect(publishSpan.attributes[SEMATTRS_MESSAGING_DESTINATION_KIND]).toBe( - MESSAGINGDESTINATIONKINDVALUES_TOPIC - ); - expect(publishSpan.attributes[SEMATTRS_MESSAGING_DESTINATION]).toBe( - topicV3Name - ); - expect(publishSpan.attributes['messaging.destination.name']).toBe( - topicV3ARN - ); - expect(publishSpan.attributes[SEMATTRS_RPC_METHOD]).toBe('Publish'); - expect(publishSpan.attributes[SEMATTRS_MESSAGING_SYSTEM]).toBe('aws.sns'); - expect(publishSpan.kind).toBe(SpanKind.PRODUCER); - }); - - it('phone number', async () => { - const PhoneNumber = 'my phone number'; - await sns.publish({ - Message: 'sns message', - PhoneNumber, - }); - - const publishSpans = getTestSpans().filter( - (s: ReadableSpan) => s.name === 'phone_number send' - ); - expect(publishSpans.length).toBe(1); - const publishSpan = publishSpans[0]; - expect(publishSpan.attributes[SEMATTRS_MESSAGING_DESTINATION]).toBe( - PhoneNumber - ); - }); - }); -}); diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/sqs.test.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/test/sqs.test.ts deleted file mode 100644 index e97d82bb2c..0000000000 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/sqs.test.ts +++ /dev/null @@ -1,607 +0,0 @@ -/* - * 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. - */ -import { AwsInstrumentation, AwsSdkSqsProcessHookInformation } from '../src'; -import { - getTestSpans, - registerInstrumentationTesting, -} from '@opentelemetry/contrib-test-utils'; -const instrumentation = registerInstrumentationTesting( - new AwsInstrumentation() -); -import * as AWS from 'aws-sdk'; -import { AWSError } from 'aws-sdk'; -import type { SQS } from 'aws-sdk'; - -import { - MESSAGINGDESTINATIONKINDVALUES_QUEUE, - MESSAGINGOPERATIONVALUES_PROCESS, - MESSAGINGOPERATIONVALUES_RECEIVE, - SEMATTRS_HTTP_STATUS_CODE, - SEMATTRS_MESSAGING_DESTINATION, - SEMATTRS_MESSAGING_DESTINATION_KIND, - SEMATTRS_MESSAGING_MESSAGE_ID, - SEMATTRS_MESSAGING_OPERATION, - SEMATTRS_MESSAGING_SYSTEM, - SEMATTRS_MESSAGING_URL, - SEMATTRS_RPC_METHOD, - SEMATTRS_RPC_SERVICE, - SEMATTRS_RPC_SYSTEM, -} from '@opentelemetry/semantic-conventions'; -import { - context, - SpanKind, - SpanStatusCode, - trace, - Span, -} from '@opentelemetry/api'; -import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { mockV2AwsSend } from './testing-utils'; -import { Message } from 'aws-sdk/clients/sqs'; -import { expect } from 'expect'; -import * as sinon from 'sinon'; -import * as messageAttributes from '../src/services/MessageAttributes'; -import { AttributeNames } from '../src/enums'; - -const responseMockSuccess = { - requestId: '0000000000000', - error: null, - httpResponse: { statusCode: 200 }, -} as AWS.Response; - -const extractContextSpy = sinon.spy( - messageAttributes, - 'extractPropagationContext' -); - -describe('SQS', () => { - before(() => { - AWS.config.credentials = { - accessKeyId: 'test key id', - expired: false, - expireTime: new Date(), - secretAccessKey: 'test acc key', - sessionToken: 'test token', - }; - }); - - beforeEach(() => { - mockV2AwsSend(responseMockSuccess, { - Messages: [{ Body: 'msg 1 payload' }, { Body: 'msg 2 payload' }], - } as AWS.SQS.Types.ReceiveMessageResult); - }); - - describe('receive context', () => { - const createReceiveChildSpan = () => { - const childSpan = trace - .getTracerProvider() - .getTracer('default') - .startSpan('child span of SQS.ReceiveMessage'); - childSpan.end(); - }; - - const expectReceiverWithChildSpan = (spans: ReadableSpan[]) => { - const awsReceiveSpan = spans.filter(s => s.kind === SpanKind.CONSUMER); - expect(awsReceiveSpan.length).toBe(1); - const internalSpan = spans.filter(s => s.kind === SpanKind.INTERNAL); - expect(internalSpan.length).toBe(1); - expect(internalSpan[0].parentSpanId).toStrictEqual( - awsReceiveSpan[0].spanContext().spanId - ); - }; - - it('should set parent context in sqs receive callback', done => { - const sqs = new AWS.SQS(); - sqs.receiveMessage( - { - QueueUrl: 'queue/url/for/unittests', - }, - (err: AWSError, data: AWS.SQS.Types.ReceiveMessageResult) => { - expect(err).toBeFalsy(); - createReceiveChildSpan(); - expectReceiverWithChildSpan(getTestSpans()); - done(); - } - ); - }); - - it("should set parent context in sqs receive 'send' callback", done => { - const sqs = new AWS.SQS(); - sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .send((err: AWSError, data: AWS.SQS.Types.ReceiveMessageResult) => { - expect(err).toBeFalsy(); - createReceiveChildSpan(); - expectReceiverWithChildSpan(getTestSpans()); - done(); - }); - }); - - it('should set parent context in sqs receive promise then', async () => { - const sqs = new AWS.SQS(); - await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise() - .then(() => { - createReceiveChildSpan(); - expectReceiverWithChildSpan(getTestSpans()); - }); - }); - - it.skip('should set parent context in sqs receive after await', async () => { - const sqs = new AWS.SQS(); - await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - - createReceiveChildSpan(); - expectReceiverWithChildSpan(getTestSpans()); - }); - - it.skip('should set parent context in sqs receive from async function', async () => { - const asycnReceive = async () => { - const sqs = new AWS.SQS(); - return await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - }; - - await asycnReceive(); - createReceiveChildSpan(); - expectReceiverWithChildSpan(getTestSpans()); - }); - }); - - describe('process spans', () => { - let receivedMessages: Message[]; - - const createProcessChildSpan = (msgContext: any) => { - const processChildSpan = trace - .getTracerProvider() - .getTracer('default') - .startSpan(`child span of sqs processing span of msg ${msgContext}`); - processChildSpan.end(); - }; - - const expectReceiver2ProcessWithNChildrenEach = ( - spans: ReadableSpan[], - numChildPerProcessSpan: number - ) => { - const awsReceiveSpan = spans.filter( - s => - s.attributes[SEMATTRS_MESSAGING_OPERATION] === - MESSAGINGOPERATIONVALUES_RECEIVE - ); - expect(awsReceiveSpan.length).toBe(1); - - const processSpans = spans.filter( - s => - s.attributes[SEMATTRS_MESSAGING_OPERATION] === - MESSAGINGOPERATIONVALUES_PROCESS - ); - expect(processSpans.length).toBe(2); - expect(processSpans[0].parentSpanId).toStrictEqual( - awsReceiveSpan[0].spanContext().spanId - ); - expect(processSpans[1].parentSpanId).toStrictEqual( - awsReceiveSpan[0].spanContext().spanId - ); - - const processChildSpans = spans.filter(s => s.kind === SpanKind.INTERNAL); - expect(processChildSpans.length).toBe(2 * numChildPerProcessSpan); - for (let i = 0; i < numChildPerProcessSpan; i++) { - expect(processChildSpans[2 * i + 0].parentSpanId).toStrictEqual( - processSpans[0].spanContext().spanId - ); - expect(processChildSpans[2 * i + 1].parentSpanId).toStrictEqual( - processSpans[1].spanContext().spanId - ); - } - }; - - const expectReceiver2ProcessWith1ChildEach = (spans: ReadableSpan[]) => { - expectReceiver2ProcessWithNChildrenEach(spans, 1); - }; - - const expectReceiver2ProcessWith2ChildEach = (spans: ReadableSpan[]) => { - expectReceiver2ProcessWithNChildrenEach(spans, 2); - }; - - const contextKeyFromTest = Symbol('context key from test'); - const contextValueFromTest = 'context value from test'; - - beforeEach(async () => { - const sqs = new AWS.SQS(); - await context.with( - context.active().setValue(contextKeyFromTest, contextValueFromTest), - async () => { - const res = await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - receivedMessages = res.Messages!; - } - ); - }); - - it('should create processing child with forEach', async () => { - receivedMessages.forEach(msg => { - createProcessChildSpan(msg.Body); - }); - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it('should create processing child with map', async () => { - receivedMessages.map(msg => { - createProcessChildSpan(msg.Body); - }); - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it('should not fail when mapping to non-object type', async () => { - receivedMessages - .map(msg => 'map result is string') - .map(s => 'some other string'); - }); - - it('should not fail when mapping to undefined type', async () => { - receivedMessages.map(msg => undefined).map(s => 'some other string'); - }); - - it('should create one processing child when throws in map', async () => { - try { - receivedMessages.map(msg => { - createProcessChildSpan(msg.Body); - throw Error('error from array.map'); - }); - } catch (err) {} - - const processChildSpans = getTestSpans().filter( - s => s.kind === SpanKind.INTERNAL - ); - expect(processChildSpans.length).toBe(1); - }); - - it('should create processing child with two forEach', async () => { - receivedMessages.forEach(msg => { - createProcessChildSpan(msg.Body); - }); - receivedMessages.forEach(msg => { - createProcessChildSpan(msg.Body); - }); - expectReceiver2ProcessWith2ChildEach(getTestSpans()); - }); - - it('should forward all parameters to forEach callback', async () => { - const objectForThis = {}; - receivedMessages.forEach(function (this: any, msg, index, array) { - expect(msg).not.toBeUndefined(); - expect(index).toBeLessThan(2); - expect(index).toBeGreaterThanOrEqual(0); - expect(array).toBe(receivedMessages); - expect(this).toBe(objectForThis); - }, objectForThis); - }); - - it('should create one processing child with forEach that throws', async () => { - try { - receivedMessages.forEach(msg => { - createProcessChildSpan(msg.Body); - throw Error('error from forEach'); - }); - } catch (err) {} - const processChildSpans = getTestSpans().filter( - s => s.kind === SpanKind.INTERNAL - ); - expect(processChildSpans.length).toBe(1); - }); - - it.skip('should create processing child with array index access', async () => { - for (let i = 0; i < receivedMessages.length; i++) { - const msg = receivedMessages[i]; - createProcessChildSpan(msg.Body); - } - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it('should create processing child with map and forEach calls', async () => { - receivedMessages - .map(msg => ({ payload: msg.Body })) - .forEach(msgBody => { - createProcessChildSpan(msgBody); - }); - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it('should create processing child with filter and forEach', async () => { - receivedMessages - .filter(msg => msg) - .forEach(msgBody => { - createProcessChildSpan(msgBody); - }); - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it.skip('should create processing child with for(msg of messages)', () => { - for (const msg of receivedMessages) { - createProcessChildSpan(msg.Body); - } - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it.skip('should create processing child with array.values() for loop', () => { - for (const msg of receivedMessages.values()) { - createProcessChildSpan(msg.Body); - } - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it.skip('should create processing child with array.values() for loop and awaits in process', async () => { - for (const msg of receivedMessages.values()) { - await new Promise(resolve => setImmediate(resolve)); - createProcessChildSpan(msg.Body); - } - expectReceiver2ProcessWith1ChildEach(getTestSpans()); - }); - - it('should propagate the context of the receive call in process spans loop', async () => { - receivedMessages.forEach(() => { - expect(context.active().getValue(contextKeyFromTest)).toStrictEqual( - contextValueFromTest - ); - }); - }); - }); - - describe('hooks', () => { - it('sqsResponseHook for sendMessage should add messaging attributes', async () => { - const region = 'us-east-1'; - const sqs = new AWS.SQS(); - sqs.config.update({ region }); - - const QueueName = 'unittest'; - const params = { - QueueUrl: `queue/url/for/${QueueName}`, - MessageBody: 'payload example from v2 without batch', - }; - - const response = await sqs.sendMessage(params).promise(); - - expect(getTestSpans().length).toBe(1); - const [span] = getTestSpans(); - - // make sure we have the general aws attributes: - expect(span.attributes[SEMATTRS_RPC_SYSTEM]).toEqual('aws-api'); - expect(span.attributes[SEMATTRS_RPC_METHOD]).toEqual('SendMessage'); - expect(span.attributes[SEMATTRS_RPC_SERVICE]).toEqual('SQS'); - expect(span.attributes[AttributeNames.AWS_REGION]).toEqual(region); - - // custom messaging attributes - expect(span.attributes[SEMATTRS_MESSAGING_SYSTEM]).toEqual('aws.sqs'); - expect(span.attributes[SEMATTRS_MESSAGING_DESTINATION_KIND]).toEqual( - MESSAGINGDESTINATIONKINDVALUES_QUEUE - ); - expect(span.attributes[SEMATTRS_MESSAGING_DESTINATION]).toEqual( - QueueName - ); - expect(span.attributes[SEMATTRS_MESSAGING_URL]).toEqual(params.QueueUrl); - expect(span.attributes[SEMATTRS_MESSAGING_MESSAGE_ID]).toEqual( - response.MessageId - ); - expect(span.attributes[SEMATTRS_HTTP_STATUS_CODE]).toEqual(200); - }); - - it('sqsProcessHook called and add message attribute to span', async () => { - const config = { - sqsProcessHook: ( - span: Span, - sqsProcessInfo: AwsSdkSqsProcessHookInformation - ) => { - span.setAttribute( - 'attribute from sqs process hook', - sqsProcessInfo.message.Body! - ); - }, - }; - - instrumentation.setConfig(config); - - const sqs = new AWS.SQS(); - const res = await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - res.Messages?.map( - message => 'some mapping to create child process spans' - ); - - const processSpans = getTestSpans().filter( - s => - s.attributes[SEMATTRS_MESSAGING_OPERATION] === - MESSAGINGOPERATIONVALUES_PROCESS - ); - expect(processSpans.length).toBe(2); - expect( - processSpans[0].attributes['attribute from sqs process hook'] - ).toBe('msg 1 payload'); - expect( - processSpans[1].attributes['attribute from sqs process hook'] - ).toBe('msg 2 payload'); - }); - - it('sqsProcessHook not set in config', async () => { - const sqs = new AWS.SQS(); - const res = await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - res.Messages?.map( - message => 'some mapping to create child process spans' - ); - const processSpans = getTestSpans().filter( - s => - s.attributes[SEMATTRS_MESSAGING_OPERATION] === - MESSAGINGOPERATIONVALUES_PROCESS - ); - expect(processSpans.length).toBe(2); - }); - - it('sqsProcessHook throws does not fail span', async () => { - const config = { - sqsProcessHook: ( - span: Span, - sqsProcessInfo: AwsSdkSqsProcessHookInformation - ) => { - throw new Error('error from sqsProcessHook hook'); - }, - }; - instrumentation.setConfig(config); - - const sqs = new AWS.SQS(); - const res = await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - res.Messages?.map( - message => 'some mapping to create child process spans' - ); - - const processSpans = getTestSpans().filter( - s => - s.attributes[SEMATTRS_MESSAGING_OPERATION] === - MESSAGINGOPERATIONVALUES_PROCESS - ); - expect(processSpans.length).toBe(2); - expect(processSpans[0].status.code).toStrictEqual(SpanStatusCode.UNSET); - expect(processSpans[1].status.code).toStrictEqual(SpanStatusCode.UNSET); - }); - - it('bogus sendMessageBatch input should not crash', async () => { - const region = 'us-east-1'; - const sqs = new AWS.SQS(); - sqs.config.update({ region }); - - const QueueName = 'unittest'; - const params = { - QueueUrl: `queue/url/for/${QueueName}`, - Entries: { Key1: { MessageBody: 'This is the first message' } }, - }; - await sqs - .sendMessageBatch(params as unknown as SQS.SendMessageBatchRequest) - .promise(); - - const spans = getTestSpans(); - expect(spans.length).toBe(1); - - // Spot check a single attribute as a sanity check. - expect(spans[0].attributes[SEMATTRS_RPC_METHOD]).toEqual( - 'SendMessageBatch' - ); - }); - }); - - describe('extract payload', () => { - beforeEach(() => { - extractContextSpy.resetHistory(); - }); - it('should not extract from payload even if set', async () => { - mockV2AwsSend(responseMockSuccess, { - Messages: [{ Body: JSON.stringify({ traceparent: 1 }) }], - } as AWS.SQS.Types.ReceiveMessageResult); - - const sqs = new AWS.SQS(); - await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests1', - }) - .promise(); - expect(extractContextSpy.returnValues[0]?.traceparent).toBeUndefined(); - }); - - it('should extract from payload', async () => { - const traceparent = { - traceparent: { - Value: '00-a1d050b7c8ad93c405e7a0d94cda5b03-23a485dc98b24027-01', - Type: 'String', - }, - }; - instrumentation.setConfig({ - sqsExtractContextPropagationFromPayload: true, - }); - mockV2AwsSend(responseMockSuccess, { - Messages: [ - { Body: JSON.stringify({ MessageAttributes: { traceparent } }) }, - ], - } as AWS.SQS.Types.ReceiveMessageResult); - - const sqs = new AWS.SQS(); - await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - expect(extractContextSpy.returnValues[0]?.traceparent).toStrictEqual( - traceparent - ); - }); - - it('should not extract from payload but from attributes', async () => { - const traceparentInPayload = 'some-trace-parent-value'; - const traceparentInMessageAttributes = { - traceparent: { - StringValue: - '00-a1d050b7c8ad93c405e7a0d94cda5b03-23a485dc98b24027-01', - DataType: 'String', - }, - }; - instrumentation.setConfig({ - sqsExtractContextPropagationFromPayload: false, - }); - mockV2AwsSend(responseMockSuccess, { - Messages: [ - { - MessageAttributes: traceparentInMessageAttributes, - Body: JSON.stringify({ - MessageAttributes: { traceparentInPayload }, - }), - }, - ], - } as AWS.SQS.Types.ReceiveMessageResult); - - const sqs = new AWS.SQS(); - await sqs - .receiveMessage({ - QueueUrl: 'queue/url/for/unittests', - }) - .promise(); - - expect(extractContextSpy.returnValues[0]).toBe( - traceparentInMessageAttributes - ); - }); - }); -}); diff --git a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/testing-utils.ts b/plugins/node/opentelemetry-instrumentation-aws-sdk/test/testing-utils.ts deleted file mode 100644 index 7ce6d373cc..0000000000 --- a/plugins/node/opentelemetry-instrumentation-aws-sdk/test/testing-utils.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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. - */ -import { context } from '@opentelemetry/api'; -import { isTracingSuppressed } from '@opentelemetry/core'; -import { getInstrumentation } from '@opentelemetry/contrib-test-utils'; -import { expect } from 'expect'; -import * as AWS from 'aws-sdk'; - -// we want to mock the request object and trigger events on it's events emitter. -// the event emitter is not part of the public interface, so we create a type -// for the mock to use it. -type CompleteEventHandler = (response: AWS.Response) => void; -type RequestWithEvents = AWS.Request & { - _events: { complete: CompleteEventHandler[] }; -}; - -export const mockV2AwsSend = ( - sendResult: any, - data: any = undefined, - expectedInstrumentationSuppressed = false -) => { - // since we are setting a new value to a function being patched by the instrumentation, - // we need to disable and enable again to make the patch for the new function. - // I would like to see another pattern for this in the future, for example - patching only - // once and just setting the result and data, or patching the http layer instead with nock package. - getInstrumentation()?.disable(); - AWS.Request.prototype.send = function ( - this: RequestWithEvents, - cb?: (error: any, response: any) => void - ) { - expect(isTracingSuppressed(context.active())).toStrictEqual( - expectedInstrumentationSuppressed - ); - if (cb) { - (this as AWS.Request).on('complete', response => { - cb(response.error, response); - }); - } - const response = { - ...sendResult, - data, - request: this, - }; - setImmediate(() => { - this._events.complete.forEach( - (handler: (response: AWS.Response) => void) => - handler(response) - ); - }); - return response; - }; - - AWS.Request.prototype.promise = function (this: RequestWithEvents) { - expect(isTracingSuppressed(context.active())).toStrictEqual( - expectedInstrumentationSuppressed - ); - const response = { - ...sendResult, - data, - request: this, - }; - setImmediate(() => { - this._events.complete.forEach( - (handler: (response: AWS.Response) => void) => - handler(response) - ); - }); - return new Promise(resolve => - setImmediate(() => { - resolve(data); - }) - ); - }; - getInstrumentation()?.enable(); -};