From 8600230bb13f12f0ea613213c4b288566f84bb90 Mon Sep 17 00:00:00 2001 From: liustve Date: Thu, 20 Feb 2025 19:20:12 +0000 Subject: [PATCH 01/13] SigV4 Support --- .../package.json | 2 + .../src/aws-opentelemetry-configurator.ts | 14 ++ .../src/otlp-aws-span-exporter.ts | 82 ++++++++ package-lock.json | 192 +++++++++++++++++- 4 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index 632ce527..9826aa13 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -79,6 +79,8 @@ "@aws-sdk/client-sfn": "^3.632.0", "@aws-sdk/client-sns": "^3.632.0", "@opentelemetry/contrib-test-utils": "0.41.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", "@types/mocha": "7.0.2", "@types/node": "18.6.5", "@types/sinon": "10.0.18", diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index 6a6a797b..e3566f69 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -57,10 +57,13 @@ import { AwsBatchUnsampledSpanProcessor } from './aws-batch-unsampled-span-proce import { AwsMetricAttributesSpanExporterBuilder } from './aws-metric-attributes-span-exporter-builder'; import { AwsSpanMetricsProcessorBuilder } from './aws-span-metrics-processor-builder'; import { OTLPUdpSpanExporter } from './otlp-udp-exporter'; +import { OTLPAwsSpanExporter } from './otlp-aws-span-exporter'; import { AwsXRayRemoteSampler } from './sampler/aws-xray-remote-sampler'; // This file is generated via `npm run compile` import { LIB_VERSION } from './version'; +const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray.([a-z0-9-]+).amazonaws.com/v1/traces$'; + const APPLICATION_SIGNALS_ENABLED_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED'; const APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT'; const METRIC_EXPORT_INTERVAL_CONFIG: string = 'OTEL_METRIC_EXPORT_INTERVAL'; @@ -422,6 +425,7 @@ export class AwsSpanProcessorProvider { private resource: Resource; static configureOtlp(): SpanExporter { + const otlp_exporter_traces_endpoint = process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT']; // eslint-disable-next-line @typescript-eslint/typedef let protocol = this.getOtlpProtocol(); @@ -438,12 +442,18 @@ export class AwsSpanProcessorProvider { case 'http/json': return new OTLPHttpTraceExporter(); case 'http/protobuf': + if (otlp_exporter_traces_endpoint && isXrayOtlpEndpoint(otlp_exporter_traces_endpoint)) { + return new OTLPAwsSpanExporter(otlp_exporter_traces_endpoint); + } return new OTLPProtoTraceExporter(); case 'udp': diag.debug('Detected AWS Lambda environment and enabling UDPSpanExporter'); return new OTLPUdpSpanExporter(getXrayDaemonEndpoint(), FORMAT_OTEL_SAMPLED_TRACES_BINARY_PREFIX); default: diag.warn(`Unsupported OTLP traces protocol: ${protocol}. Using http/protobuf.`); + if (otlp_exporter_traces_endpoint && isXrayOtlpEndpoint(otlp_exporter_traces_endpoint)) { + return new OTLPAwsSpanExporter(otlp_exporter_traces_endpoint); + } return new OTLPProtoTraceExporter(); } } @@ -652,4 +662,8 @@ function getXrayDaemonEndpoint() { return process.env[AWS_XRAY_DAEMON_ADDRESS_CONFIG]; } +function isXrayOtlpEndpoint(otlpEndpoint: string | undefined) { + return otlpEndpoint && new RegExp(XRAY_OTLP_ENDPOINT_PATTERN).test(otlpEndpoint.toLowerCase()); +} + // END The OpenTelemetry Authors code diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts new file mode 100644 index 00000000..3d38ece8 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts @@ -0,0 +1,82 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; +import { diag } from '@opentelemetry/api'; +import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; +import { ExportResult } from '@opentelemetry/core'; +import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import { Sha256 } from '@aws-crypto/sha256-js'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { SignatureV4 } from '@smithy/signature-v4'; +import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer'; +import { HttpRequest } from '@smithy/protocol-http'; + +const SERVICE_NAME = 'xray'; + +/** + * This exporter extends the functionality of the OTLPProtoTraceExporter to allow spans to be exported + * to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the aws-sdk + * library to sign and directly inject SigV4 Authentication to the exported request's headers. ... + */ +export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter { + private endpoint: string; + private region: string; + + constructor(endpoint: string, config?: OTLPExporterNodeConfigBase) { + super(config); + this.region = endpoint.split('.')[1]; + this.endpoint = endpoint; + } + + /** + * Overrides the upstream implementation of export. All behaviors are the same except if the + * endpoint is an XRay OTLP endpoint, we will sign the request with SigV4 in headers before + * sending it to the endpoint. Otherwise, we will skip signing. + */ + public override async export(items: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise { + const url = new URL(this.endpoint); + const serializedSpans: Uint8Array | undefined = ProtobufTraceSerializer.serializeRequest(items); + + if (serializedSpans === undefined) { + return; + } + + /* + This is bad practice but there is no other way to access and inject SigV4 headers + into the request headers before the traces get exported. + */ + const oldHeaders = (this as any)._transport._transport._parameters.headers; + + const request = new HttpRequest({ + method: 'POST', + protocol: 'https', + hostname: url.hostname, + path: url.pathname, + body: serializedSpans, + headers: { + ...oldHeaders, + host: url.hostname, + }, + }); + + try { + const signer = new SignatureV4({ + credentials: defaultProvider(), + region: this.region, + service: SERVICE_NAME, + sha256: Sha256, + }); + + const signedRequest = await signer.sign(request); + + (this as any)._transport._transport._parameters.headers = signedRequest.headers; + } catch (exception) { + diag.debug( + `Failed to sign/authenticate the given exported Span request to OTLP XRay endpoint with error: ${exception}` + ); + } + + await super.export(items, resultCallback); + } +} diff --git a/package-lock.json b/package-lock.json index 941386f0..14ba937c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "@opentelemetry/id-generator-aws-xray": "1.2.2", "@opentelemetry/instrumentation": "0.53.0", "@opentelemetry/instrumentation-aws-sdk": "0.44.0", + "@opentelemetry/otlp-transformer": "0.53.0", "@opentelemetry/propagator-aws-xray": "1.26.0", "@opentelemetry/resource-detector-aws": "1.6.1", "@opentelemetry/resources": "1.26.0", @@ -68,6 +69,8 @@ "@aws-sdk/client-sfn": "^3.632.0", "@aws-sdk/client-sns": "^3.632.0", "@opentelemetry/contrib-test-utils": "0.41.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", "@types/mocha": "7.0.2", "@types/node": "18.6.5", "@types/sinon": "10.0.18", @@ -245,6 +248,189 @@ "node": ">=16.0.0" } }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@aws-sdk/client-bedrock-agent-runtime/node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@aws-sdk/client-bedrock-agent/node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@aws-sdk/client-bedrock/node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/protocol-http": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", + "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/protocol-http/node_modules/@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/signature-v4": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", + "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/signature-v4/node_modules/@smithy/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/signature-v4/node_modules/@smithy/util-middleware": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", + "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "aws-distro-opentelemetry-node-autoinstrumentation/node_modules/typescript": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", @@ -6774,9 +6960,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", "dev": true, "license": "Apache-2.0", "dependencies": { From f9cca5b1121a2901952f5bcd0ffb8a6197ceae1a Mon Sep 17 00:00:00 2001 From: liustve Date: Thu, 20 Feb 2025 19:38:59 +0000 Subject: [PATCH 02/13] added escaped '.' --- .../src/aws-opentelemetry-configurator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index e3566f69..3621745b 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -62,7 +62,7 @@ import { AwsXRayRemoteSampler } from './sampler/aws-xray-remote-sampler'; // This file is generated via `npm run compile` import { LIB_VERSION } from './version'; -const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray.([a-z0-9-]+).amazonaws.com/v1/traces$'; +const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$'; const APPLICATION_SIGNALS_ENABLED_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED'; const APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT'; From d84b956c0fcbac9fd0cdeb3c66b5010fc20056e4 Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 06:55:43 +0000 Subject: [PATCH 03/13] added unit testing, made dependencies optional --- .../package.json | 2 + .../src/aws-opentelemetry-configurator.ts | 2 +- .../src/otlp-aws-span-exporter.ts | 129 +++++++++----- .../test/otlp-aws-span-exporter.test.ts | 164 ++++++++++++++++++ 4 files changed, 251 insertions(+), 46 deletions(-) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index 9826aa13..c1629f82 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -83,11 +83,13 @@ "@smithy/signature-v4": "^5.0.1", "@types/mocha": "7.0.2", "@types/node": "18.6.5", + "@types/proxyquire": "^1.3.31", "@types/sinon": "10.0.18", "expect": "29.2.0", "mocha": "7.2.0", "nock": "13.2.1", "nyc": "15.1.0", + "proxyquire": "^2.1.3", "rimraf": "5.0.5", "sinon": "15.2.0", "ts-mocha": "10.0.0", diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index 3621745b..02054649 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -62,7 +62,7 @@ import { AwsXRayRemoteSampler } from './sampler/aws-xray-remote-sampler'; // This file is generated via `npm run compile` import { LIB_VERSION } from './version'; -const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray\.([a-z0-9-]+)\.amazonaws\.com/v1/traces$'; +const XRAY_OTLP_ENDPOINT_PATTERN = '^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$'; const APPLICATION_SIGNALS_ENABLED_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED'; const APPLICATION_SIGNALS_EXPORTER_ENDPOINT_CONFIG: string = 'OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT'; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts index 3d38ece8..d9f191e9 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts @@ -3,15 +3,9 @@ import { OTLPTraceExporter as OTLPProtoTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; import { diag } from '@opentelemetry/api'; import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; -import { ExportResult } from '@opentelemetry/core'; -import { defaultProvider } from '@aws-sdk/credential-provider-node'; -import { Sha256 } from '@aws-crypto/sha256-js'; -import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { SignatureV4 } from '@smithy/signature-v4'; import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer'; -import { HttpRequest } from '@smithy/protocol-http'; - -const SERVICE_NAME = 'xray'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { ExportResult } from '@opentelemetry/core'; /** * This exporter extends the functionality of the OTLPProtoTraceExporter to allow spans to be exported @@ -20,11 +14,22 @@ const SERVICE_NAME = 'xray'; * href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html">... */ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter { + private static readonly SERVICE_NAME: string = 'xray'; private endpoint: string; private region: string; + // Holds the dependencies needed to sign the SigV4 headers + private defaultProvider: any; + private sha256: any; + private signatureV4: any; + private httpRequest: any; + + // If there are required dependencies then we enable SigV4 signing. Otherwise skip it + private hasRequiredDependencies: boolean = false; + constructor(endpoint: string, config?: OTLPExporterNodeConfigBase) { - super(config); + super(OTLPAwsSpanExporter.changeUrlConfig(endpoint, config)); + this.initDependencies(); this.region = endpoint.split('.')[1]; this.endpoint = endpoint; } @@ -35,48 +40,82 @@ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter { * sending it to the endpoint. Otherwise, we will skip signing. */ public override async export(items: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise { - const url = new URL(this.endpoint); - const serializedSpans: Uint8Array | undefined = ProtobufTraceSerializer.serializeRequest(items); + // Only do SigV4 Signing if the required dependencies are installed. Otherwise default to the regular http/protobuf exporter. + if (this.hasRequiredDependencies) { + const url = new URL(this.endpoint); + const serializedSpans: Uint8Array | undefined = ProtobufTraceSerializer.serializeRequest(items); + + if (serializedSpans === undefined) { + return; + } + + /* + This is bad practice but there is no other way to access and inject SigV4 headers + into the request headers before the traces get exported. + */ + const oldHeaders = (this as any)._transport?._transport?._parameters?.headers; - if (serializedSpans === undefined) { - return; + if (oldHeaders) { + const request = new this.httpRequest({ + method: 'POST', + protocol: 'https', + hostname: url.hostname, + path: url.pathname, + body: serializedSpans, + headers: { + ...oldHeaders, + host: url.hostname, + }, + }); + + try { + const signer = new this.signatureV4({ + credentials: this.defaultProvider(), + region: this.region, + service: OTLPAwsSpanExporter.SERVICE_NAME, + sha256: this.sha256, + }); + + const signedRequest = await signer.sign(request); + + (this as any)._transport._transport._parameters.headers = signedRequest.headers; + } catch (exception) { + diag.debug( + `Failed to sign/authenticate the given exported Span request to OTLP XRay endpoint with error: ${exception}` + ); + } + } } - /* - This is bad practice but there is no other way to access and inject SigV4 headers - into the request headers before the traces get exported. - */ - const oldHeaders = (this as any)._transport._transport._parameters.headers; - - const request = new HttpRequest({ - method: 'POST', - protocol: 'https', - hostname: url.hostname, - path: url.pathname, - body: serializedSpans, - headers: { - ...oldHeaders, - host: url.hostname, - }, - }); + await super.export(items, resultCallback); + } + private initDependencies(): any { try { - const signer = new SignatureV4({ - credentials: defaultProvider(), - region: this.region, - service: SERVICE_NAME, - sha256: Sha256, - }); - - const signedRequest = await signer.sign(request); - - (this as any)._transport._transport._parameters.headers = signedRequest.headers; - } catch (exception) { - diag.debug( - `Failed to sign/authenticate the given exported Span request to OTLP XRay endpoint with error: ${exception}` - ); + const awsSdkModule = require('@aws-sdk/credential-provider-node'); + const awsCryptoModule = require('@aws-crypto/sha256-js'); + const signatureModule = require('@smithy/signature-v4'); + const httpModule = require('@smithy/protocol-http'); + + (this.defaultProvider = awsSdkModule.defaultProvider), + (this.sha256 = awsCryptoModule.Sha256), + (this.signatureV4 = signatureModule.SignatureV4), + (this.httpRequest = httpModule.HttpRequest); + this.hasRequiredDependencies = true; + } catch (error) { + diag.error(`Failed to load required AWS dependency for SigV4 Signing: ${error}`); } + } - await super.export(items, resultCallback); + private static changeUrlConfig(endpoint: string, config?: OTLPExporterNodeConfigBase) { + const newConfig = + config === undefined + ? { url: endpoint } + : { + ...config, + url: endpoint, + }; + + return newConfig; } } diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts new file mode 100644 index 00000000..0dc13c27 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -0,0 +1,164 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import expect from 'expect'; +import { OTLPAwsSpanExporter } from '../src/otlp-aws-span-exporter'; +import * as sinon from 'sinon'; +import * as proxyquire from 'proxyquire'; +import nock = require('nock'); + +const XRAY_OTLP_ENDPOINT = 'https://xray.us-east-1.amazonaws.com'; +const AUTHORIZATION_HEADER = 'Authorization'; +const X_AMZ_DATE_HEADER = 'X-Amz-Date'; +const X_AMZ_SECURITY_TOKEN_HEADER = 'X-Amz-Security-Token'; + +const EXPECTED_AUTH_HEADER = 'AWS4-HMAC-SHA256 Credential=test_key/some_date/us-east-1/xray/aws4_request'; +const EXPECTED_AUTH_X_AMZ_DATE = 'some_date'; +const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token'; + +describe('OTLPAwsSpanExporter', () => { + let sandbox: sinon.SinonSandbox; + let scope: nock.Scope; + let mockModule: any; + + beforeEach(() => { + scope = nock(XRAY_OTLP_ENDPOINT) + .post('/v1/traces') + .reply((uri: any, requestBody: any) => { + return [200, '']; + }); + + sandbox = sinon.createSandbox(); + mockModule = proxyquire('../src/otlp-aws-span-exporter', { + '@smithy/signature-v4': { + SignatureV4: class MockSignatureV4 { + sign(req: any) { + req.headers = { + ...req.headers, + [AUTHORIZATION_HEADER]: EXPECTED_AUTH_HEADER, + [X_AMZ_DATE_HEADER]: EXPECTED_AUTH_X_AMZ_DATE, + [X_AMZ_SECURITY_TOKEN_HEADER]: EXPECTED_AUTH_SECURITY_TOKEN, + }; + + return req; + } + }, + }, + '@aws-sdk/credential-provider-node': { + defaultProvider: () => async () => { + return { + accessKeyId: 'test_access_key', + secretAccessKey: 'test_secret_key', + }; + }, + }, + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('Should inject SigV4 Headers successfully', () => { + const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); + + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).toHaveProperty(AUTHORIZATION_HEADER.toLowerCase()); + expect(headers).toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()); + expect(headers).toHaveProperty(X_AMZ_DATE_HEADER.toLowerCase()); + + expect(headers[AUTHORIZATION_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_HEADER); + expect(headers[X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_SECURITY_TOKEN); + expect(headers[X_AMZ_DATE_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_X_AMZ_DATE); + + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); + + exporter.export([], () => {}); + }); + + describe('Should not inject SigV4 headers if dependencies are missing', () => { + const dependencies = [ + '@aws-sdk/credential-provider-node', + '@aws-crypto/sha256-js', + '@smithy/signature-v4', + '@smithy/protocol-http', + ]; + + dependencies.forEach(dependency => { + it(`should not sign headers if missing dependency: ${dependency}`, () => { + const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); + + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); + + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); + + Object.keys(require.cache).forEach(key => { + delete require.cache[key]; + }); + const requireStub = sandbox.stub(require('module'), '_load'); + requireStub.withArgs(dependency).throws(new Error(`Cannot find module '${dependency}'`)); + requireStub.callThrough(); + + exporter.export([], () => {}); + }); + }); + }); + + it('should not inject SigV4 headers if failure to sign headers', async () => { + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); + + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); + + const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', { + '@smithy/signature-v4': { + SignatureV4: class MockSignatureV4 { + sign() { + throw new Error('signing error'); + } + }, + }, + }); + + const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); + + exporter.export([], () => {}); + }); + + it('should not inject SigV4 headers if failure to retrieve credentials', async () => { + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); + + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); + + const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', { + '@aws-sdk/credential-provider-node': { + defaultProvider: () => async () => { + throw new Error('credentials error'); + }, + }, + }); + + const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); + + exporter.export([], () => {}); + }); +}); From f89d0883137eb26770c0bf9524b2d7c29aac5f99 Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 07:02:04 +0000 Subject: [PATCH 04/13] returning early to stop appsignals metrics --- .../src/aws-opentelemetry-configurator.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index 02054649..8c4afcef 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -238,6 +238,11 @@ export class AwsOpentelemetryConfigurator { } spanProcessors.push(AttributePropagatingSpanProcessorBuilder.create().build()); + + if(isXrayOtlpEndpoint(process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'])) { + return; + } + const applicationSignalsMetricExporter: PushMetricExporter = ApplicationSignalsExporterProvider.Instance.createExporter(); const periodicExportingMetricReader: PeriodicExportingMetricReader = new PeriodicExportingMetricReader({ From fa975730328f27ba3d46efe93ca7ff87521618a7 Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 07:03:46 +0000 Subject: [PATCH 05/13] added proxyquire --- package-lock.json | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/package-lock.json b/package-lock.json index 14ba937c..98990ae7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,11 +73,13 @@ "@smithy/signature-v4": "^5.0.1", "@types/mocha": "7.0.2", "@types/node": "18.6.5", + "@types/proxyquire": "^1.3.31", "@types/sinon": "10.0.18", "expect": "29.2.0", "mocha": "7.2.0", "nock": "13.2.1", "nyc": "15.1.0", + "proxyquire": "^2.1.3", "rimraf": "5.0.5", "sinon": "15.2.0", "ts-mocha": "10.0.0", @@ -7396,6 +7398,13 @@ "@types/pg": "*" } }, + "node_modules/@types/proxyquire": { + "version": "1.3.31", + "resolved": "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.31.tgz", + "integrity": "sha512-uALowNG2TSM1HNPMMOR0AJwv4aPYPhqB0xlEhkeRTMuto5hjoSPZkvgu1nbPUkz3gEPAHv4sy4DmKsurZiEfRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", @@ -10845,6 +10854,20 @@ "node": ">=10" } }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -12653,6 +12676,16 @@ "node": ">=8" } }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -15208,6 +15241,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -15999,6 +16042,13 @@ "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", "license": "MIT" }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -18798,6 +18848,18 @@ "dev": true, "license": "MIT" }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", From c68866a14d62567c0a04b8357ee50ed6e36fcbf7 Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 07:05:54 +0000 Subject: [PATCH 06/13] lint fix --- .../src/aws-opentelemetry-configurator.ts | 2 +- .../test/otlp-aws-span-exporter.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index 8c4afcef..5163a0df 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -239,7 +239,7 @@ export class AwsOpentelemetryConfigurator { spanProcessors.push(AttributePropagatingSpanProcessorBuilder.create().build()); - if(isXrayOtlpEndpoint(process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'])) { + if (isXrayOtlpEndpoint(process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'])) { return; } diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index 0dc13c27..45a7dac7 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -77,7 +77,7 @@ describe('OTLPAwsSpanExporter', () => { exporter.export([], () => {}); }); - + describe('Should not inject SigV4 headers if dependencies are missing', () => { const dependencies = [ '@aws-sdk/credential-provider-node', From 165078c59c6bd60d7f037898a44e69666def3d22 Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 07:12:29 +0000 Subject: [PATCH 07/13] fix async issue --- .../test/otlp-aws-span-exporter.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index 45a7dac7..1913429a 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -58,7 +58,7 @@ describe('OTLPAwsSpanExporter', () => { sandbox.restore(); }); - it('Should inject SigV4 Headers successfully', () => { + it('Should inject SigV4 Headers successfully', async () => { const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); scope.on('request', (req, interceptor, body) => { @@ -75,7 +75,7 @@ describe('OTLPAwsSpanExporter', () => { expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); }); - exporter.export([], () => {}); + await exporter.export([], () => {}); }); describe('Should not inject SigV4 headers if dependencies are missing', () => { @@ -87,7 +87,7 @@ describe('OTLPAwsSpanExporter', () => { ]; dependencies.forEach(dependency => { - it(`should not sign headers if missing dependency: ${dependency}`, () => { + it(`should not sign headers if missing dependency: ${dependency}`, async () => { const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); scope.on('request', (req, interceptor, body) => { @@ -107,7 +107,7 @@ describe('OTLPAwsSpanExporter', () => { requireStub.withArgs(dependency).throws(new Error(`Cannot find module '${dependency}'`)); requireStub.callThrough(); - exporter.export([], () => {}); + await exporter.export([], () => {}); }); }); }); @@ -135,7 +135,7 @@ describe('OTLPAwsSpanExporter', () => { const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); - exporter.export([], () => {}); + await exporter.export([], () => {}); }); it('should not inject SigV4 headers if failure to retrieve credentials', async () => { @@ -159,6 +159,6 @@ describe('OTLPAwsSpanExporter', () => { const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); - exporter.export([], () => {}); + await exporter.export([], () => {}); }); }); From 695182abb5b3e2009d9c04a739b26efd0486e927 Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 20:44:51 +0000 Subject: [PATCH 08/13] added node version detection --- .../src/otlp-aws-span-exporter.ts | 10 +- .../src/utils.ts | 19 ++ .../test/otlp-aws-span-exporter.test.ts | 244 +++++++++--------- 3 files changed, 154 insertions(+), 119 deletions(-) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts index d9f191e9..e3b5d591 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts @@ -6,12 +6,15 @@ import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer'; import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { ExportResult } from '@opentelemetry/core'; +import { getNodeVersion } from './utils'; /** * This exporter extends the functionality of the OTLPProtoTraceExporter to allow spans to be exported * to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the aws-sdk * library to sign and directly inject SigV4 Authentication to the exported request's headers. ... + * + * This only works with version >=16 Node.js environments. */ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter { private static readonly SERVICE_NAME: string = 'xray'; @@ -24,7 +27,7 @@ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter { private signatureV4: any; private httpRequest: any; - // If there are required dependencies then we enable SigV4 signing. Otherwise skip it + // If the required dependencies are installed then we enable SigV4 signing. Otherwise skip it private hasRequiredDependencies: boolean = false; constructor(endpoint: string, config?: OTLPExporterNodeConfigBase) { @@ -91,6 +94,11 @@ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter { } private initDependencies(): any { + if (getNodeVersion() < 16) { + diag.error('SigV4 signing requires atleast Node major version 16'); + return; + } + try { const awsSdkModule = require('@aws-sdk/credential-provider-node'); const awsCryptoModule = require('@aws-crypto/sha256-js'); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts new file mode 100644 index 00000000..0fd74c0d --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts @@ -0,0 +1,19 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export const getNodeVersion = () => { + const nodeVersion = process.versions.node; + const versionParts = nodeVersion.split('.'); + + if (versionParts.length === 0) { + return -1; + } + + const majorVersion = parseInt(versionParts[0], 10); + + if (isNaN(majorVersion)) { + return -1; + } + + return majorVersion; +}; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index 1913429a..0a3b1bbb 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -5,6 +5,7 @@ import { OTLPAwsSpanExporter } from '../src/otlp-aws-span-exporter'; import * as sinon from 'sinon'; import * as proxyquire from 'proxyquire'; import nock = require('nock'); +import { getNodeVersion } from '../src/utils'; const XRAY_OTLP_ENDPOINT = 'https://xray.us-east-1.amazonaws.com'; const AUTHORIZATION_HEADER = 'Authorization'; @@ -15,150 +16,157 @@ const EXPECTED_AUTH_HEADER = 'AWS4-HMAC-SHA256 Credential=test_key/some_date/us- const EXPECTED_AUTH_X_AMZ_DATE = 'some_date'; const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token'; -describe('OTLPAwsSpanExporter', () => { - let sandbox: sinon.SinonSandbox; - let scope: nock.Scope; - let mockModule: any; +const nodeVersion = getNodeVersion(); - beforeEach(() => { - scope = nock(XRAY_OTLP_ENDPOINT) - .post('/v1/traces') - .reply((uri: any, requestBody: any) => { - return [200, '']; - }); +if (nodeVersion >= 16) { + describe('OTLPAwsSpanExporter', () => { + let sandbox: sinon.SinonSandbox; + let scope: nock.Scope; + let mockModule: any; - sandbox = sinon.createSandbox(); - mockModule = proxyquire('../src/otlp-aws-span-exporter', { - '@smithy/signature-v4': { - SignatureV4: class MockSignatureV4 { - sign(req: any) { - req.headers = { - ...req.headers, - [AUTHORIZATION_HEADER]: EXPECTED_AUTH_HEADER, - [X_AMZ_DATE_HEADER]: EXPECTED_AUTH_X_AMZ_DATE, - [X_AMZ_SECURITY_TOKEN_HEADER]: EXPECTED_AUTH_SECURITY_TOKEN, - }; + beforeEach(() => { + sandbox = sinon.createSandbox(); + + scope = nock(XRAY_OTLP_ENDPOINT) + .post('/v1/traces') + .reply((uri: any, requestBody: any) => { + return [200, '']; + }); - return req; - } + mockModule = proxyquire('../src/otlp-aws-span-exporter', { + '@smithy/signature-v4': { + SignatureV4: class MockSignatureV4 { + sign(req: any) { + req.headers = { + ...req.headers, + [AUTHORIZATION_HEADER]: EXPECTED_AUTH_HEADER, + [X_AMZ_DATE_HEADER]: EXPECTED_AUTH_X_AMZ_DATE, + [X_AMZ_SECURITY_TOKEN_HEADER]: EXPECTED_AUTH_SECURITY_TOKEN, + }; + + return req; + } + }, }, - }, - '@aws-sdk/credential-provider-node': { - defaultProvider: () => async () => { - return { - accessKeyId: 'test_access_key', - secretAccessKey: 'test_secret_key', - }; + '@aws-sdk/credential-provider-node': { + defaultProvider: () => async () => { + return { + accessKeyId: 'test_access_key', + secretAccessKey: 'test_secret_key', + }; + }, }, - }, + }); }); - }); - afterEach(() => { - sandbox.restore(); - }); - - it('Should inject SigV4 Headers successfully', async () => { - const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); + afterEach(() => { + sandbox.restore(); + }); - scope.on('request', (req, interceptor, body) => { - const headers = req.headers; - expect(headers).toHaveProperty(AUTHORIZATION_HEADER.toLowerCase()); - expect(headers).toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()); - expect(headers).toHaveProperty(X_AMZ_DATE_HEADER.toLowerCase()); + it('Should inject SigV4 Headers successfully', async () => { + const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); - expect(headers[AUTHORIZATION_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_HEADER); - expect(headers[X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_SECURITY_TOKEN); - expect(headers[X_AMZ_DATE_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_X_AMZ_DATE); + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).toHaveProperty(AUTHORIZATION_HEADER.toLowerCase()); + expect(headers).toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()); + expect(headers).toHaveProperty(X_AMZ_DATE_HEADER.toLowerCase()); - expect(headers['content-type']).toBe('application/x-protobuf'); - expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); - }); + expect(headers[AUTHORIZATION_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_HEADER); + expect(headers[X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_SECURITY_TOKEN); + expect(headers[X_AMZ_DATE_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_X_AMZ_DATE); - await exporter.export([], () => {}); - }); + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); - describe('Should not inject SigV4 headers if dependencies are missing', () => { - const dependencies = [ - '@aws-sdk/credential-provider-node', - '@aws-crypto/sha256-js', - '@smithy/signature-v4', - '@smithy/protocol-http', - ]; - - dependencies.forEach(dependency => { - it(`should not sign headers if missing dependency: ${dependency}`, async () => { - const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); - - scope.on('request', (req, interceptor, body) => { - const headers = req.headers; - expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); - expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); - expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); - - expect(headers['content-type']).toBe('application/x-protobuf'); - expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); - }); + await exporter.export([], () => {}); + }); - Object.keys(require.cache).forEach(key => { - delete require.cache[key]; + describe('Should not inject SigV4 headers if dependencies are missing', () => { + const dependencies = [ + '@aws-sdk/credential-provider-node', + '@aws-crypto/sha256-js', + '@smithy/signature-v4', + '@smithy/protocol-http', + ]; + + dependencies.forEach(dependency => { + it(`should not sign headers if missing dependency: ${dependency}`, async () => { + const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); + + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); + + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); + + Object.keys(require.cache).forEach(key => { + delete require.cache[key]; + }); + const requireStub = sandbox.stub(require('module'), '_load'); + requireStub.withArgs(dependency).throws(new Error(`Cannot find module '${dependency}'`)); + requireStub.callThrough(); + + await exporter.export([], () => {}); }); - const requireStub = sandbox.stub(require('module'), '_load'); - requireStub.withArgs(dependency).throws(new Error(`Cannot find module '${dependency}'`)); - requireStub.callThrough(); - - await exporter.export([], () => {}); }); }); - }); - it('should not inject SigV4 headers if failure to sign headers', async () => { - scope.on('request', (req, interceptor, body) => { - const headers = req.headers; - expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); - expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); - expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); + it('should not inject SigV4 headers if failure to sign headers', async () => { + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); - expect(headers['content-type']).toBe('application/x-protobuf'); - expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); - }); + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); - const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', { - '@smithy/signature-v4': { - SignatureV4: class MockSignatureV4 { - sign() { - throw new Error('signing error'); - } + const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', { + '@smithy/signature-v4': { + SignatureV4: class MockSignatureV4 { + sign() { + throw new Error('signing error'); + } + }, }, - }, - }); + }); - const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); + const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); - await exporter.export([], () => {}); - }); + await exporter.export([], () => {}); + }); - it('should not inject SigV4 headers if failure to retrieve credentials', async () => { - scope.on('request', (req, interceptor, body) => { - const headers = req.headers; - expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); - expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); - expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); + it('should not inject SigV4 headers if failure to retrieve credentials', async () => { + scope.on('request', (req, interceptor, body) => { + const headers = req.headers; + expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER); + expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER); - expect(headers['content-type']).toBe('application/x-protobuf'); - expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); - }); + expect(headers['content-type']).toBe('application/x-protobuf'); + expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/); + }); - const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', { - '@aws-sdk/credential-provider-node': { - defaultProvider: () => async () => { - throw new Error('credentials error'); + const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', { + '@aws-sdk/credential-provider-node': { + defaultProvider: () => async () => { + throw new Error('credentials error'); + }, }, - }, - }); + }); - const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); + const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT); - await exporter.export([], () => {}); + await exporter.export([], () => {}); + }); }); -}); +} else { + it.skip(`Skipping tests - Node.js version ${nodeVersion} is below required version 16`, () => {}); +} From 37e3d7783e4555c5717b17c07cf3cefe4d41bc8c Mon Sep 17 00:00:00 2001 From: liustve Date: Mon, 24 Feb 2025 20:51:23 +0000 Subject: [PATCH 09/13] adding test coverage ignore if node version less than 16 --- .../test/otlp-aws-span-exporter.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index 0a3b1bbb..0685c11f 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -18,7 +18,10 @@ const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token'; const nodeVersion = getNodeVersion(); -if (nodeVersion >= 16) { +/* istanbul ignore if */ +if (nodeVersion < 16) { + it.skip(`Skipping tests - Node.js version ${nodeVersion} is below required version 16`, () => {}); +} else { describe('OTLPAwsSpanExporter', () => { let sandbox: sinon.SinonSandbox; let scope: nock.Scope; @@ -167,6 +170,4 @@ if (nodeVersion >= 16) { await exporter.export([], () => {}); }); }); -} else { - it.skip(`Skipping tests - Node.js version ${nodeVersion} is below required version 16`, () => {}); } From 4465b863109ee8638476965cff1bd61ec0652274 Mon Sep 17 00:00:00 2001 From: liustve Date: Tue, 25 Feb 2025 20:32:09 +0000 Subject: [PATCH 10/13] linting fix --- .../src/aws-opentelemetry-configurator.ts | 2 +- .../test/otlp-aws-span-exporter.test.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts index 5163a0df..15180552 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts @@ -56,8 +56,8 @@ import { AttributePropagatingSpanProcessorBuilder } from './attribute-propagatin import { AwsBatchUnsampledSpanProcessor } from './aws-batch-unsampled-span-processor'; import { AwsMetricAttributesSpanExporterBuilder } from './aws-metric-attributes-span-exporter-builder'; import { AwsSpanMetricsProcessorBuilder } from './aws-span-metrics-processor-builder'; -import { OTLPUdpSpanExporter } from './otlp-udp-exporter'; import { OTLPAwsSpanExporter } from './otlp-aws-span-exporter'; +import { OTLPUdpSpanExporter } from './otlp-udp-exporter'; import { AwsXRayRemoteSampler } from './sampler/aws-xray-remote-sampler'; // This file is generated via `npm run compile` import { LIB_VERSION } from './version'; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index 0685c11f..34332977 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -4,10 +4,11 @@ import expect from 'expect'; import { OTLPAwsSpanExporter } from '../src/otlp-aws-span-exporter'; import * as sinon from 'sinon'; import * as proxyquire from 'proxyquire'; -import nock = require('nock'); +import * as nock from 'nock'; import { getNodeVersion } from '../src/utils'; const XRAY_OTLP_ENDPOINT = 'https://xray.us-east-1.amazonaws.com'; +const XRAY_OTLP_ENDPOINT_PATH = '/v1/traces'; const AUTHORIZATION_HEADER = 'Authorization'; const X_AMZ_DATE_HEADER = 'X-Amz-Date'; const X_AMZ_SECURITY_TOKEN_HEADER = 'X-Amz-Security-Token'; @@ -18,7 +19,7 @@ const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token'; const nodeVersion = getNodeVersion(); -/* istanbul ignore if */ +/* istanbul ignore else */ if (nodeVersion < 16) { it.skip(`Skipping tests - Node.js version ${nodeVersion} is below required version 16`, () => {}); } else { @@ -31,7 +32,7 @@ if (nodeVersion < 16) { sandbox = sinon.createSandbox(); scope = nock(XRAY_OTLP_ENDPOINT) - .post('/v1/traces') + .post(XRAY_OTLP_ENDPOINT_PATH) .reply((uri: any, requestBody: any) => { return [200, '']; }); @@ -67,7 +68,7 @@ if (nodeVersion < 16) { }); it('Should inject SigV4 Headers successfully', async () => { - const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); + const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + XRAY_OTLP_ENDPOINT_PATH); scope.on('request', (req, interceptor, body) => { const headers = req.headers; @@ -96,7 +97,7 @@ if (nodeVersion < 16) { dependencies.forEach(dependency => { it(`should not sign headers if missing dependency: ${dependency}`, async () => { - const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces'); + const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + XRAY_OTLP_ENDPOINT_PATH); scope.on('request', (req, interceptor, body) => { const headers = req.headers; From 0490762bc2392aa9ca6510bac4630c346e89bd0d Mon Sep 17 00:00:00 2001 From: liustve Date: Tue, 25 Feb 2025 20:36:47 +0000 Subject: [PATCH 11/13] removed it.skip() test --- .../test/otlp-aws-span-exporter.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index 34332977..ead4fa00 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -19,10 +19,8 @@ const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token'; const nodeVersion = getNodeVersion(); -/* istanbul ignore else */ -if (nodeVersion < 16) { - it.skip(`Skipping tests - Node.js version ${nodeVersion} is below required version 16`, () => {}); -} else { +/* istanbul ignore next */ +if (nodeVersion >= 16) { describe('OTLPAwsSpanExporter', () => { let sandbox: sinon.SinonSandbox; let scope: nock.Scope; From caa75d93abe9e2e6d3c49448bd41f2d9364102d3 Mon Sep 17 00:00:00 2001 From: liustve Date: Tue, 25 Feb 2025 20:38:20 +0000 Subject: [PATCH 12/13] removed it.skip() for nodejs environments < 16 --- .../test/otlp-aws-span-exporter.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts index ead4fa00..b2cdb2df 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts @@ -19,6 +19,7 @@ const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token'; const nodeVersion = getNodeVersion(); +// SigV4 exporter requires packages that require Node environments >= 16 /* istanbul ignore next */ if (nodeVersion >= 16) { describe('OTLPAwsSpanExporter', () => { From 18d0b5412123213f2842b59fd21016f79e6dd851 Mon Sep 17 00:00:00 2001 From: liustve Date: Tue, 25 Feb 2025 21:24:45 +0000 Subject: [PATCH 13/13] excluding otlp-aws-span-exporter.ts from test coverage --- aws-distro-opentelemetry-node-autoinstrumentation/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/package.json b/aws-distro-opentelemetry-node-autoinstrumentation/package.json index c1629f82..16382efc 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/package.json +++ b/aws-distro-opentelemetry-node-autoinstrumentation/package.json @@ -40,7 +40,8 @@ "src/**/*.ts" ], "exclude": [ - "src/third-party/**/*.ts" + "src/third-party/**/*.ts", + "src/otlp-aws-span-exporter.ts" ] }, "bugs": {