From 583cc7c23d4a95eccdd1bdedc5ed5ab018f6b7e4 Mon Sep 17 00:00:00 2001 From: Marylia Gutierrez Date: Mon, 15 Apr 2024 04:28:48 -0400 Subject: [PATCH 1/6] feat(resources): implements `service.instance.id` (#4608) * feat(node-sdk): implements `service.instance.id` Implements `service.instance.id` Signed-off-by: maryliag * implement service instance id by default with env var use an experimental environment variable to set the default value of `service.instance.id` as default * Update CHANGELOG.md Co-authored-by: Marc Pichler * create separate add experimental for browser and node create a function to add experimental default values (currently only service instance id, but others can be added in the future), with the value being set on the node case, but not on the browser case. Signed-off-by: maryliag * use resource detector for service instance id use a resource detector for service instance id * remove references to env variables * remove Detector and use DetectorSync remove the Detector and use the DetectorSync instead. Also mark things as experimental. Signed-off-by: maryliag * Update packages/opentelemetry-resources/src/platform/node/ServiceInstanceIDDetectorSync.ts Co-authored-by: Marc Pichler * rename ServiceInstanceIDDetector to ServiceInstanceIDDetectorSync * change ID to Id * renaming something temp so git will pick up the name change it was not picking up the case change on previous commit * update to final name using Id --------- Signed-off-by: maryliag Co-authored-by: Marc Pichler --- CHANGELOG.md | 3 +- .../opentelemetry-sdk-node/test/sdk.test.ts | 91 ++++++++++++++++++- package-lock.json | 15 --- .../opentelemetry-resources/src/Resource.ts | 21 +++-- .../src/detectors/EnvDetectorSync.ts | 4 +- .../browser/ServiceInstanceIdDetectorSync.ts | 22 +++++ .../src/platform/browser/index.ts | 1 + .../node/ServiceInstanceIdDetectorSync.ts | 40 ++++++++ .../src/platform/node/index.ts | 1 + 9 files changed, 167 insertions(+), 31 deletions(-) create mode 100644 packages/opentelemetry-resources/src/platform/browser/ServiceInstanceIdDetectorSync.ts create mode 100644 packages/opentelemetry-resources/src/platform/node/ServiceInstanceIdDetectorSync.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1ec010f1..286a23dba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :rocket: (Enhancement) -feat(sdk-trace-base): log resource attributes in ConsoleSpanExporter [#4605](https://github.com/open-telemetry/opentelemetry-js/pull/4605) @pichlermarc +* feat(sdk-trace-base): log resource attributes in ConsoleSpanExporter [#4605](https://github.com/open-telemetry/opentelemetry-js/pull/4605) @pichlermarc +* feat(resources): new experimental detector ServiceInstanceIdDetectorSync that sets the value for `service.instance.id` as random UUID. ### :bug: (Bug Fix) diff --git a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts index d76534450f..078a33d9cc 100644 --- a/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -59,6 +59,7 @@ import { processDetector, hostDetector, Resource, + serviceInstanceIdDetectorSync, } from '@opentelemetry/resources'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { logs } from '@opentelemetry/api-logs'; @@ -70,6 +71,7 @@ import { import { SEMRESATTRS_HOST_NAME, SEMRESATTRS_PROCESS_PID, + SEMRESATTRS_SERVICE_INSTANCE_ID, } from '@opentelemetry/semantic-conventions'; const DefaultContextManager = semver.gte(process.version, '14.8.0') @@ -126,6 +128,7 @@ describe('Node SDK', () => { assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider)); assert.ok(!(logs.getLoggerProvider() instanceof LoggerProvider)); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('should register a diag logger with OTEL_LOG_LEVEL', () => { @@ -145,6 +148,7 @@ describe('Node SDK', () => { }); delete env.OTEL_LOG_LEVEL; + sdk.shutdown(); }); it('should not register a diag logger with OTEL_LOG_LEVEL unset', () => { @@ -158,6 +162,7 @@ describe('Node SDK', () => { sdk.start(); assert.strictEqual(spy.callCount, 0); + sdk.shutdown(); }); it('should register a tracer provider if an exporter is provided', async () => { @@ -180,6 +185,7 @@ describe('Node SDK', () => { const apiTracerProvider = trace.getTracerProvider() as ProxyTracerProvider; assert.ok(apiTracerProvider.getDelegate() instanceof NodeTracerProvider); + await sdk.shutdown(); }); it('should register a tracer provider if an exporter is provided via env', async () => { @@ -203,6 +209,7 @@ describe('Node SDK', () => { trace.getTracerProvider() as ProxyTracerProvider; assert.ok(apiTracerProvider.getDelegate() instanceof NodeTracerProvider); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('should register a tracer provider if span processors are provided', async () => { @@ -240,6 +247,7 @@ describe('Node SDK', () => { assert(listOfProcessors[0] instanceof NoopSpanProcessor); assert(listOfProcessors[1] instanceof SimpleSpanProcessor); assert(listOfProcessors[2] instanceof BatchSpanProcessor); + await sdk.shutdown(); }); it('should register a meter provider if a reader is provided', async () => { @@ -446,6 +454,7 @@ describe('Node SDK', () => { namespace: 'default', version: '0.0.1', }); + await sdk.shutdown(); }); }); @@ -497,6 +506,7 @@ describe('Node SDK', () => { namespace: 'default', version: '0.0.1', }); + await sdk.shutdown(); }); }); @@ -551,6 +561,7 @@ describe('Node SDK', () => { /{\s+"service\.instance\.id":\s+"627cc493",\s+"service\.name":\s+"my-service",\s+"service\.namespace":\s+"default",\s+"service\.version":\s+"0.0.1"\s+}\s*/gm ) ); + await sdk.shutdown(); }); describe('with a faulty environment variable', () => { @@ -578,6 +589,7 @@ describe('Node SDK', () => { 'EnvDetector failed: Attribute value should be a ASCII string with a length not exceed 255 characters.' ) ); + await sdk.shutdown(); }); }); }); @@ -595,6 +607,7 @@ describe('Node SDK', () => { assertServiceResource(resource, { name: 'config-set-name', }); + await sdk.shutdown(); }); it('should configure service name via OTEL_SERVICE_NAME env var', async () => { @@ -609,6 +622,7 @@ describe('Node SDK', () => { name: 'env-set-name', }); delete process.env.OTEL_SERVICE_NAME; + await sdk.shutdown(); }); it('should favor config set service name over OTEL_SERVICE_NAME env set service name', async () => { @@ -625,11 +639,12 @@ describe('Node SDK', () => { name: 'config-set-name', }); delete process.env.OTEL_SERVICE_NAME; + await sdk.shutdown(); }); it('should configure service name via OTEL_RESOURCE_ATTRIBUTES env var', async () => { process.env.OTEL_RESOURCE_ATTRIBUTES = - 'service.name=resource-env-set-name'; + 'service.name=resource-env-set-name,service.instance.id=my-instance-id'; const sdk = new NodeSDK(); sdk.start(); @@ -638,13 +653,15 @@ describe('Node SDK', () => { assertServiceResource(resource, { name: 'resource-env-set-name', + instanceId: 'my-instance-id', }); delete process.env.OTEL_RESOURCE_ATTRIBUTES; + await sdk.shutdown(); }); it('should favor config set service name over OTEL_RESOURCE_ATTRIBUTES env set service name', async () => { process.env.OTEL_RESOURCE_ATTRIBUTES = - 'service.name=resource-env-set-name'; + 'service.name=resource-env-set-name,service.instance.id=my-instance-id'; const sdk = new NodeSDK({ serviceName: 'config-set-name', }); @@ -655,8 +672,55 @@ describe('Node SDK', () => { assertServiceResource(resource, { name: 'config-set-name', + instanceId: 'my-instance-id', }); delete process.env.OTEL_RESOURCE_ATTRIBUTES; + await sdk.shutdown(); + }); + }); + + describe('configureServiceInstanceId', async () => { + it('should configure service instance id via OTEL_RESOURCE_ATTRIBUTES env var', async () => { + process.env.OTEL_RESOURCE_ATTRIBUTES = + 'service.instance.id=627cc493,service.name=my-service'; + const sdk = new NodeSDK(); + + sdk.start(); + const resource = sdk['_resource']; + await resource.waitForAsyncAttributes?.(); + + assertServiceResource(resource, { + name: 'my-service', + instanceId: '627cc493', + }); + delete process.env.OTEL_RESOURCE_ATTRIBUTES; + sdk.shutdown(); + }); + + it('should configure service instance id with random UUID', async () => { + const sdk = new NodeSDK({ + autoDetectResources: true, + resourceDetectors: [ + processDetector, + envDetector, + hostDetector, + serviceInstanceIdDetectorSync, + ], + }); + + sdk.start(); + const resource = sdk['_resource']; + await resource.waitForAsyncAttributes?.(); + + const UUID_REGEX = + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + assert.equal( + UUID_REGEX.test( + resource.attributes[SEMRESATTRS_SERVICE_INSTANCE_ID]?.toString() || '' + ), + true + ); + await sdk.shutdown(); }); }); @@ -671,7 +735,7 @@ describe('Node SDK', () => { it('should not register a trace provider', async () => { const sdk = new NodeSDK({}); - await sdk.start(); + sdk.start(); assert.strictEqual( (trace.getTracerProvider() as ProxyTracerProvider).getDelegate(), @@ -694,7 +758,7 @@ describe('Node SDK', () => { metricReader: metricReader, autoDetectResources: false, }); - await sdk.start(); + sdk.start(); assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider)); @@ -730,6 +794,7 @@ describe('Node SDK', () => { await resource.waitForAsyncAttributes?.(); assert.deepStrictEqual(resource, Resource.empty()); + await sdk.shutdown(); }); }); }); @@ -758,6 +823,7 @@ describe('Node SDK', () => { assert.strictEqual(span.spanContext().spanId, 'constant-test-span-id'); assert.strictEqual(span.spanContext().traceId, 'constant-test-trace-id'); + await sdk.shutdown(); }); }); }); @@ -786,6 +852,7 @@ describe('setup exporter from env', () => { assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); assert(listOfProcessors.length === 1); assert(listOfProcessors[0] instanceof BatchSpanProcessor); + await sdk.shutdown(); }); it('ignore env exporter when user provides exporter to sdk config', async () => { const traceExporter = new ConsoleSpanExporter(); @@ -802,6 +869,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors.length === 1); assert(listOfProcessors[0] instanceof SimpleSpanProcessor === false); assert(listOfProcessors[0] instanceof BatchSpanProcessor); + await sdk.shutdown(); }); it('ignores default env exporter when user provides span processor to sdk config', async () => { const traceExporter = new ConsoleSpanExporter(); @@ -819,6 +887,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors.length === 1); assert(listOfProcessors[0] instanceof SimpleSpanProcessor); assert(listOfProcessors[0] instanceof BatchSpanProcessor === false); + await sdk.shutdown(); }); it('ignores env exporter when user provides tracer exporter to sdk config and sets exporter via env', async () => { env.OTEL_TRACES_EXPORTER = 'console'; @@ -837,6 +906,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors[0] instanceof SimpleSpanProcessor === false); assert(listOfProcessors[0] instanceof BatchSpanProcessor); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('should only create one span processor when configured using env vars and config', async () => { env.OTEL_TRACES_EXPORTER = 'console'; @@ -852,6 +922,7 @@ describe('setup exporter from env', () => { ); assert.strictEqual(listOfProcessors.length, 1); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('use otlp exporter and defined exporter protocol env value', async () => { env.OTEL_TRACES_EXPORTER = 'otlp'; @@ -866,6 +937,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors[0] instanceof BatchSpanProcessor); delete env.OTEL_TRACES_EXPORTER; delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + await sdk.shutdown(); }); it('use noop span processor when user sets env exporter to none', async () => { env.OTEL_TRACES_EXPORTER = 'none'; @@ -879,6 +951,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors.length === 0); assert(activeProcessor instanceof NoopSpanProcessor); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('log warning that sdk will not be initialized when exporter is set to none', async () => { env.OTEL_TRACES_EXPORTER = 'none'; @@ -890,16 +963,18 @@ describe('setup exporter from env', () => { 'OTEL_TRACES_EXPORTER contains "none". SDK will not be initialized.' ); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('use default otlp exporter when user does not set exporter via env or config', async () => { const sdk = new NodeSDK(); - await sdk.start(); + sdk.start(); const listOfProcessors = sdk['_tracerProvider']!['_registeredSpanProcessors']!; assert(sdk['_tracerProvider'] instanceof TracerProviderWithEnvExporters); assert(listOfProcessors.length === 1); assert(listOfProcessors[0] instanceof BatchSpanProcessor); + await sdk.shutdown(); }); it('use default otlp exporter when empty value is provided for exporter via env', async () => { env.OTEL_TRACES_EXPORTER = ''; @@ -912,6 +987,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors.length === 1); assert(listOfProcessors[0] instanceof BatchSpanProcessor); env.OTEL_TRACES_EXPORTER = ''; + await sdk.shutdown(); }); it('use only default exporter when none value is provided with other exporters', async () => { @@ -926,6 +1002,7 @@ describe('setup exporter from env', () => { assert(listOfProcessors[0] instanceof BatchSpanProcessor); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('log warning that only default exporter will be used since exporter list contains none with other exports ', async () => { env.OTEL_TRACES_EXPORTER = 'otlp,zipkin,none'; @@ -937,6 +1014,7 @@ describe('setup exporter from env', () => { 'OTEL_TRACES_EXPORTER contains "none" along with other exporters. Using default otlp exporter.' ); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('should warn that provided exporter value is unrecognized and not able to be set up', async () => { env.OTEL_TRACES_EXPORTER = 'invalid'; @@ -954,6 +1032,7 @@ describe('setup exporter from env', () => { ); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); it('setup zipkin, jaeger and otlp exporters', async () => { env.OTEL_TRACES_EXPORTER = 'zipkin, otlp, jaeger'; @@ -971,6 +1050,7 @@ describe('setup exporter from env', () => { delete env.OTEL_TRACES_EXPORTER; delete env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL; + await sdk.shutdown(); }); it('use the console exporter', async () => { env.OTEL_TRACES_EXPORTER = 'console, otlp'; @@ -983,5 +1063,6 @@ describe('setup exporter from env', () => { assert(listOfProcessors[0] instanceof SimpleSpanProcessor); assert(listOfProcessors[1] instanceof BatchSpanProcessor); delete env.OTEL_TRACES_EXPORTER; + await sdk.shutdown(); }); }); diff --git a/package-lock.json b/package-lock.json index bbae633a5d..73a66d070e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4807,15 +4807,6 @@ "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, - "experimental/packages/sdk-logs/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.0.tgz", - "integrity": "sha512-po7penSfQ/Z8352lRVDpaBrd9znwA5mHGqXR7nDEiVnxkDFkBIhVf/tKeAJDIq/erFpcRowKFeCsr5eqqcSyFQ==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "experimental/packages/sdk-logs/node_modules/@webpack-cli/configtest": { "version": "2.1.1", "dev": true, @@ -43543,12 +43534,6 @@ } } }, - "@opentelemetry/semantic-conventions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.0.tgz", - "integrity": "sha512-po7penSfQ/Z8352lRVDpaBrd9znwA5mHGqXR7nDEiVnxkDFkBIhVf/tKeAJDIq/erFpcRowKFeCsr5eqqcSyFQ==", - "dev": true - }, "@webpack-cli/configtest": { "version": "2.1.1", "dev": true, diff --git a/packages/opentelemetry-resources/src/Resource.ts b/packages/opentelemetry-resources/src/Resource.ts index dfddda8ae1..d44dbacc4d 100644 --- a/packages/opentelemetry-resources/src/Resource.ts +++ b/packages/opentelemetry-resources/src/Resource.ts @@ -15,7 +15,12 @@ */ import { diag } from '@opentelemetry/api'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { + SEMRESATTRS_SERVICE_NAME, + SEMRESATTRS_TELEMETRY_SDK_LANGUAGE, + SEMRESATTRS_TELEMETRY_SDK_NAME, + SEMRESATTRS_TELEMETRY_SDK_VERSION, +} from '@opentelemetry/semantic-conventions'; import { SDK_INFO } from '@opentelemetry/core'; import { ResourceAttributes } from './types'; import { defaultServiceName } from './platform'; @@ -51,13 +56,13 @@ export class Resource implements IResource { */ static default(): IResource { return new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: defaultServiceName(), - [SemanticResourceAttributes.TELEMETRY_SDK_LANGUAGE]: - SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_LANGUAGE], - [SemanticResourceAttributes.TELEMETRY_SDK_NAME]: - SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_NAME], - [SemanticResourceAttributes.TELEMETRY_SDK_VERSION]: - SDK_INFO[SemanticResourceAttributes.TELEMETRY_SDK_VERSION], + [SEMRESATTRS_SERVICE_NAME]: defaultServiceName(), + [SEMRESATTRS_TELEMETRY_SDK_LANGUAGE]: + SDK_INFO[SEMRESATTRS_TELEMETRY_SDK_LANGUAGE], + [SEMRESATTRS_TELEMETRY_SDK_NAME]: + SDK_INFO[SEMRESATTRS_TELEMETRY_SDK_NAME], + [SEMRESATTRS_TELEMETRY_SDK_VERSION]: + SDK_INFO[SEMRESATTRS_TELEMETRY_SDK_VERSION], }); } diff --git a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts index 8572323a09..329d489ec4 100644 --- a/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts +++ b/packages/opentelemetry-resources/src/detectors/EnvDetectorSync.ts @@ -16,7 +16,7 @@ import { diag } from '@opentelemetry/api'; import { getEnv } from '@opentelemetry/core'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; import { Resource } from '../Resource'; import { DetectorSync, ResourceAttributes } from '../types'; import { ResourceDetectionConfig } from '../config'; @@ -70,7 +70,7 @@ class EnvDetectorSync implements DetectorSync { } if (serviceName) { - attributes[SemanticResourceAttributes.SERVICE_NAME] = serviceName; + attributes[SEMRESATTRS_SERVICE_NAME] = serviceName; } return new Resource(attributes); diff --git a/packages/opentelemetry-resources/src/platform/browser/ServiceInstanceIdDetectorSync.ts b/packages/opentelemetry-resources/src/platform/browser/ServiceInstanceIdDetectorSync.ts new file mode 100644 index 0000000000..d79fa1f4ea --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/browser/ServiceInstanceIdDetectorSync.ts @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { noopDetectorSync } from '../../detectors/NoopDetectorSync'; + +/** + * @experimental + */ +export const serviceInstanceIdDetectorSync = noopDetectorSync; diff --git a/packages/opentelemetry-resources/src/platform/browser/index.ts b/packages/opentelemetry-resources/src/platform/browser/index.ts index 9ed1b0151b..47f5cf301e 100644 --- a/packages/opentelemetry-resources/src/platform/browser/index.ts +++ b/packages/opentelemetry-resources/src/platform/browser/index.ts @@ -21,3 +21,4 @@ export * from './HostDetectorSync'; export * from './OSDetectorSync'; export * from './ProcessDetector'; export * from './ProcessDetectorSync'; +export * from './ServiceInstanceIdDetectorSync'; diff --git a/packages/opentelemetry-resources/src/platform/node/ServiceInstanceIdDetectorSync.ts b/packages/opentelemetry-resources/src/platform/node/ServiceInstanceIdDetectorSync.ts new file mode 100644 index 0000000000..9196bcba51 --- /dev/null +++ b/packages/opentelemetry-resources/src/platform/node/ServiceInstanceIdDetectorSync.ts @@ -0,0 +1,40 @@ +/* + * 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 { SEMRESATTRS_SERVICE_INSTANCE_ID } from '@opentelemetry/semantic-conventions'; +import { Resource } from '../../Resource'; +import { DetectorSync, ResourceAttributes } from '../../types'; +import { ResourceDetectionConfig } from '../../config'; +import { randomUUID } from 'crypto'; + +/** + * ServiceInstanceIdDetectorSync detects the resources related to the service instance ID. + */ +class ServiceInstanceIdDetectorSync implements DetectorSync { + detect(_config?: ResourceDetectionConfig): Resource { + const attributes: ResourceAttributes = { + [SEMRESATTRS_SERVICE_INSTANCE_ID]: randomUUID(), + }; + + return new Resource(attributes); + } +} + +/** + * @experimental + */ +export const serviceInstanceIdDetectorSync = + new ServiceInstanceIdDetectorSync(); diff --git a/packages/opentelemetry-resources/src/platform/node/index.ts b/packages/opentelemetry-resources/src/platform/node/index.ts index 9ed1b0151b..47f5cf301e 100644 --- a/packages/opentelemetry-resources/src/platform/node/index.ts +++ b/packages/opentelemetry-resources/src/platform/node/index.ts @@ -21,3 +21,4 @@ export * from './HostDetectorSync'; export * from './OSDetectorSync'; export * from './ProcessDetector'; export * from './ProcessDetectorSync'; +export * from './ServiceInstanceIdDetectorSync'; From fab27d578a3e08a1515d76bedd4d9411296e3690 Mon Sep 17 00:00:00 2001 From: Marc Pichler Date: Mon, 15 Apr 2024 10:32:28 +0200 Subject: [PATCH 2/6] chore(renovate): enable lock file maintainance (#4628) --- renovate.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/renovate.json b/renovate.json index 4c5698cf22..dc4ed25ec8 100644 --- a/renovate.json +++ b/renovate.json @@ -21,6 +21,10 @@ "dependencyDashboardApproval": true } ], + "lockFileMaintenance": { + "enabled": true, + "schedule": ["before 3am on Monday"] + }, "ignoreDeps": ["@opentelemetry/api", "@opentelemetry/resources_1.9.0", "@types/node"], "assignees": ["@blumamir", "@dyladan", "@legendecas", "@pichlermarc"], "labels": ["dependencies"] From da02c8d6b150998318498e3532d42b2619221f4f Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 15 Apr 2024 13:12:55 +0200 Subject: [PATCH 3/6] fix: Don't use `require` to load `package.json` files (#4593) * fix: Don't use require to load package.json files * update changelog * Move changelog entry --------- Co-authored-by: Marc Pichler --- experimental/CHANGELOG.md | 1 + .../src/platform/node/instrumentation.ts | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 48dc912ca2..c2ad7bcaed 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to experimental packages in this project will be documented ### :bug: (Bug Fix) * fix(otlp-grpc-exporter-base): avoid TypeError on exporter shutdown [#4612](https://github.com/open-telemetry/opentelemetry-js/pull/4612) +* fix(instrumentation): Don't use `require` to load `package.json` files ### :books: (Refine Doc) diff --git a/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts b/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts index c639bc8bd4..2fe151a1fe 100644 --- a/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts +++ b/experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts @@ -30,6 +30,7 @@ import { InstrumentationModuleDefinition } from '../../types'; import { diag } from '@opentelemetry/api'; import type { OnRequireFn } from 'require-in-the-middle'; import { Hook } from 'require-in-the-middle'; +import { readFileSync } from 'fs'; /** * Base abstract class for instrumenting node plugins @@ -160,8 +161,10 @@ export abstract class InstrumentationBase private _extractPackageVersion(baseDir: string): string | undefined { try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const version = require(path.join(baseDir, 'package.json')).version; + const json = readFileSync(path.join(baseDir, 'package.json'), { + encoding: 'utf8', + }); + const version = JSON.parse(json).version; return typeof version === 'string' ? version : undefined; } catch (error) { diag.warn('Failed extracting version', baseDir); From 3b5eb239cc2612d2181f4661a5be6f60552026b3 Mon Sep 17 00:00:00 2001 From: Hyun Oh Date: Mon, 15 Apr 2024 20:13:45 +0900 Subject: [PATCH 4/6] feat(sdk-logs): make dropping attribute print message (#4614) * feat(sdk-logs): make dropping attribute print message * chore: update changelog * chore(sdk-logs): add comment to explain message logic * Update experimental/CHANGELOG.md --------- Co-authored-by: Marc Pichler --- experimental/CHANGELOG.md | 1 + .../packages/sdk-logs/src/LogRecord.ts | 4 ++ .../sdk-logs/test/common/LogRecord.test.ts | 67 ++++++++++++------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index c2ad7bcaed..c94f75f1ad 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to experimental packages in this project will be documented ### :rocket: (Enhancement) * feat(otlp-transformer): consolidate scope/resource creation in transformer [#4600](https://github.com/open-telemetry/opentelemetry-js/pull/4600) +* feat(sdk-logs): print message when attributes are dropped due to attribute count limit [#4614](https://github.com/open-telemetry/opentelemetry-js/pull/4614) @HyunnoH ### :bug: (Bug Fix) diff --git a/experimental/packages/sdk-logs/src/LogRecord.ts b/experimental/packages/sdk-logs/src/LogRecord.ts index abc4f45285..e7704ec389 100644 --- a/experimental/packages/sdk-logs/src/LogRecord.ts +++ b/experimental/packages/sdk-logs/src/LogRecord.ts @@ -140,6 +140,10 @@ export class LogRecord implements ReadableLogRecord { this._logRecordLimits.attributeCountLimit && !Object.prototype.hasOwnProperty.call(this.attributes, key) ) { + // This logic is to create drop message at most once per LogRecord to prevent excessive logging. + if (this.droppedAttributesCount === 1) { + api.diag.warn('Dropping extra attributes.'); + } return this; } if (isAttributeValue(value)) { diff --git a/experimental/packages/sdk-logs/test/common/LogRecord.test.ts b/experimental/packages/sdk-logs/test/common/LogRecord.test.ts index 5a0acaa6db..3518e99fd1 100644 --- a/experimental/packages/sdk-logs/test/common/LogRecord.test.ts +++ b/experimental/packages/sdk-logs/test/common/LogRecord.test.ts @@ -177,32 +177,31 @@ describe('LogRecord', () => { describe('when logRecordLimits options set', () => { describe('when "attributeCountLimit" option defined', () => { - const { logRecord } = setup({ attributeCountLimit: 100 }); - for (let i = 0; i < 150; i++) { - let attributeValue; - switch (i % 3) { - case 0: { - attributeValue = `bar${i}`; - break; - } - case 1: { - attributeValue = [`bar${i}`]; - break; - } - case 2: { - attributeValue = { - bar: `bar${i}`, - }; - break; - } - default: { - attributeValue = `bar${i}`; + it('should remove / drop all remaining values after the number of values exceeds this limit', () => { + const { logRecord } = setup({ attributeCountLimit: 100 }); + for (let i = 0; i < 150; i++) { + let attributeValue; + switch (i % 3) { + case 0: { + attributeValue = `bar${i}`; + break; + } + case 1: { + attributeValue = [`bar${i}`]; + break; + } + case 2: { + attributeValue = { + bar: `bar${i}`, + }; + break; + } + default: { + attributeValue = `bar${i}`; + } } + logRecord.setAttribute(`foo${i}`, attributeValue); } - logRecord.setAttribute(`foo${i}`, attributeValue); - } - - it('should remove / drop all remaining values after the number of values exceeds this limit', () => { const { attributes, droppedAttributesCount } = logRecord; assert.strictEqual(Object.keys(attributes).length, 100); assert.strictEqual(attributes.foo0, 'bar0'); @@ -212,6 +211,26 @@ describe('LogRecord', () => { assert.strictEqual(attributes.foo149, undefined); assert.strictEqual(droppedAttributesCount, 50); }); + + it('should not print message when there are no dropped attributes', () => { + const warnStub = sinon.spy(diag, 'warn'); + const { logRecord } = setup({ attributeCountLimit: 10 }); + for (let i = 0; i < 7; i++) { + logRecord.setAttribute(`foo${i}`, `bar${i}`); + } + sinon.assert.callCount(warnStub, 0); + warnStub.restore(); + }); + + it('should print message only once when attribute(s) are dropped', () => { + const warnStub = sinon.spy(diag, 'warn'); + const { logRecord } = setup({ attributeCountLimit: 5 }); + for (let i = 0; i < 7; i++) { + logRecord.setAttribute(`foo${i}`, `bar${i}`); + } + sinon.assert.callCount(warnStub, 1); + warnStub.restore(); + }); }); describe('when "attributeValueLengthLimit" option defined', () => { From 87e25c5e015f86d0a26a596e20ed1195f8f5fec3 Mon Sep 17 00:00:00 2001 From: Marc Pichler Date: Mon, 15 Apr 2024 13:34:34 +0200 Subject: [PATCH 5/6] fix: ensure api is not dropped from workspaces in package-lock.json (#4623) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1db653d12..3c8e5d3348 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "comment_internal": "echo scripts below this line are for internal use", "_check:no_changes": "if [ ! -z \"$(git status -uall --porcelain)\" ]; then echo Please ensure all changes are committed; exit 1; fi", "_backup:package-json": "cp package.json package.json.backup", - "_restore:package-json": "mv package.json.backup package.json", + "_restore:package-json": "mv package.json.backup package.json && npm install --package-lock-only", "_lerna:remove_api": "node -e 'var fs=require(\"fs\");var p=require(\"./package.json\");p.workspaces=p.workspaces.filter(p=>p!==\"api\");fs.writeFileSync(\"package.json\",JSON.stringify(p,null,2))'", "_lerna:remove_stable": "node -e 'var fs=require(\"fs\");var p=require(\"./package.json\");p.workspaces=p.workspaces.filter(p=>p!==\"packages/*\");fs.writeFileSync(\"package.json\",JSON.stringify(p,null,2))'", "_lerna:version_patch": "npx lerna version patch --exact --no-git-tag-version --no-push --yes", From 7f82b80f3c377de622b1543e8c13a5a853d52c5d Mon Sep 17 00:00:00 2001 From: Martin Kuba Date: Tue, 16 Apr 2024 01:53:51 -0700 Subject: [PATCH 6/6] Move xray propagator from contrib (no history) (#4603) * moved aws xray propagator from contrib * updated package lock file * updated dev dependencies * added a note about the original location in README * updated changelog * fix: limit package-lock.json changes * removed status section from readme * chore: align versions with current release --------- Co-authored-by: Marc Pichler --- CHANGELOG.md | 1 + package-lock.json | 262 ++++++++++++++ packages/propagator-aws-xray/.eslintignore | 1 + packages/propagator-aws-xray/.eslintrc.js | 9 + packages/propagator-aws-xray/LICENSE | 201 +++++++++++ packages/propagator-aws-xray/README.md | 74 ++++ packages/propagator-aws-xray/karma.conf.js | 24 ++ packages/propagator-aws-xray/package.json | 85 +++++ .../src/AWSXRayPropagator.ts | 200 +++++++++++ packages/propagator-aws-xray/src/index.ts | 17 + .../test/AWSXRayPropagator.test.ts | 328 ++++++++++++++++++ .../propagator-aws-xray/test/index-webpack.ts | 20 ++ .../propagator-aws-xray/tsconfig.esm.json | 19 + .../propagator-aws-xray/tsconfig.esnext.json | 19 + packages/propagator-aws-xray/tsconfig.json | 19 + tsconfig.esm.json | 3 + tsconfig.esnext.json | 3 + tsconfig.json | 4 + 18 files changed, 1289 insertions(+) create mode 100644 packages/propagator-aws-xray/.eslintignore create mode 100644 packages/propagator-aws-xray/.eslintrc.js create mode 100644 packages/propagator-aws-xray/LICENSE create mode 100644 packages/propagator-aws-xray/README.md create mode 100644 packages/propagator-aws-xray/karma.conf.js create mode 100644 packages/propagator-aws-xray/package.json create mode 100644 packages/propagator-aws-xray/src/AWSXRayPropagator.ts create mode 100644 packages/propagator-aws-xray/src/index.ts create mode 100644 packages/propagator-aws-xray/test/AWSXRayPropagator.test.ts create mode 100644 packages/propagator-aws-xray/test/index-webpack.ts create mode 100644 packages/propagator-aws-xray/tsconfig.esm.json create mode 100644 packages/propagator-aws-xray/tsconfig.esnext.json create mode 100644 packages/propagator-aws-xray/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 286a23dba3..50e66e0532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/ ### :rocket: (Enhancement) * feat(sdk-trace-base): log resource attributes in ConsoleSpanExporter [#4605](https://github.com/open-telemetry/opentelemetry-js/pull/4605) @pichlermarc +* feat(propagator-aws-xray): moved AWS Xray propagator from contrib [4603](https://github.com/open-telemetry/opentelemetry-js/pull/4603) @martinkuba * feat(resources): new experimental detector ServiceInstanceIdDetectorSync that sets the value for `service.instance.id` as random UUID. ### :bug: (Bug Fix) diff --git a/package-lock.json b/package-lock.json index 73a66d070e..21ee872526 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10031,6 +10031,10 @@ "resolved": "experimental/packages/otlp-transformer", "link": true }, + "node_modules/@opentelemetry/propagator-aws-xray": { + "resolved": "packages/propagator-aws-xray", + "link": true + }, "node_modules/@opentelemetry/propagator-b3": { "resolved": "packages/opentelemetry-propagator-b3", "link": true @@ -35470,6 +35474,15 @@ "@opentelemetry/api": ">=1.0.0 <1.9.0" } }, + "packages/opentelemetry-resources/node_modules/@opentelemetry/api": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "packages/opentelemetry-resources/node_modules/@opentelemetry/resources_1.9.0": { "name": "@opentelemetry/resources", "version": "1.9.0", @@ -36392,6 +36405,154 @@ "@opentelemetry/api": ">=1.0.0 <1.9.0" } }, + "packages/propagator-aws-xray": { + "name": "@opentelemetry/propagator-aws-xray", + "version": "1.23.0", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.23.0" + }, + "devDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0", + "@types/mocha": "10.0.6", + "@types/node": "18.6.5", + "@types/webpack-env": "1.16.3", + "babel-plugin-istanbul": "6.1.1", + "codecov": "3.8.3", + "cross-var": "1.1.0", + "karma": "6.4.2", + "karma-chrome-launcher": "3.1.0", + "karma-coverage": "2.2.1", + "karma-mocha": "2.0.1", + "karma-spec-reporter": "0.0.36", + "karma-webpack": "4.0.2", + "lerna": "6.6.2", + "mocha": "10.2.0", + "nyc": "15.1.0", + "ts-loader": "8.4.0", + "ts-mocha": "10.0.0", + "typescript": "4.4.4", + "webpack": "5.89.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0" + } + }, + "packages/propagator-aws-xray/node_modules/enhanced-resolve": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "packages/propagator-aws-xray/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "packages/propagator-aws-xray/node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "packages/propagator-aws-xray/node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "packages/propagator-aws-xray/node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "packages/sdk-metrics": { "name": "@opentelemetry/sdk-metrics", "version": "1.23.0", @@ -43181,6 +43342,101 @@ } } }, + "@opentelemetry/propagator-aws-xray": { + "version": "file:packages/propagator-aws-xray", + "requires": { + "@opentelemetry/api": ">=1.0.0 <1.9.0", + "@opentelemetry/core": "1.23.0", + "@types/mocha": "10.0.6", + "@types/node": "18.6.5", + "@types/webpack-env": "1.16.3", + "babel-plugin-istanbul": "6.1.1", + "codecov": "3.8.3", + "cross-var": "1.1.0", + "karma": "6.4.2", + "karma-chrome-launcher": "3.1.0", + "karma-coverage": "2.2.1", + "karma-mocha": "2.0.1", + "karma-spec-reporter": "0.0.36", + "karma-webpack": "4.0.2", + "lerna": "6.6.2", + "mocha": "10.2.0", + "nyc": "15.1.0", + "ts-loader": "8.4.0", + "ts-mocha": "10.0.0", + "typescript": "4.4.4", + "webpack": "5.89.0" + }, + "dependencies": { + "enhanced-resolve": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + } + }, + "webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + } + } + }, "@opentelemetry/propagator-b3": { "version": "file:packages/opentelemetry-propagator-b3", "requires": { @@ -43317,6 +43573,12 @@ "webpack-merge": "5.10.0" }, "dependencies": { + "@opentelemetry/api": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==", + "dev": true + }, "@opentelemetry/resources_1.9.0": { "version": "npm:@opentelemetry/resources@1.9.0", "dev": true, diff --git a/packages/propagator-aws-xray/.eslintignore b/packages/propagator-aws-xray/.eslintignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/packages/propagator-aws-xray/.eslintignore @@ -0,0 +1 @@ +build diff --git a/packages/propagator-aws-xray/.eslintrc.js b/packages/propagator-aws-xray/.eslintrc.js new file mode 100644 index 0000000000..36847df9fb --- /dev/null +++ b/packages/propagator-aws-xray/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + "env": { + "mocha": true, + "commonjs": true, + "node": true, + "browser": true + }, + ...require('../../eslint.base.js') +} diff --git a/packages/propagator-aws-xray/LICENSE b/packages/propagator-aws-xray/LICENSE new file mode 100644 index 0000000000..e50e8c80f9 --- /dev/null +++ b/packages/propagator-aws-xray/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2022] 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 + + http://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. diff --git a/packages/propagator-aws-xray/README.md b/packages/propagator-aws-xray/README.md new file mode 100644 index 0000000000..fb66fb6ca4 --- /dev/null +++ b/packages/propagator-aws-xray/README.md @@ -0,0 +1,74 @@ +# OpenTelemetry Propagator AWS X-Ray + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +The OpenTelemetry Propagator for AWS X-Ray provides HTTP header propagation for systems that are using AWS `X-Amzn-Trace-Id` format. +This propagator translates the OpenTelemetry SpanContext into the equivalent AWS header format, for use with the OpenTelemetry JS SDK. +`TraceState` is currently not propagated. + +This package was originally located in [opentelemetry-js-contrib](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/propagators/opentelemetry-propagator-aws-xray). It has been moved in order to make it a direct dependency of the Node SDK. As a result, versions from 1.4.0 to 1.22.0 have been skipped. + +## Installation + +```sh +npm install --save @opentelemetry/propagator-aws-xray +``` + +## Usage + +In the [global tracer configuration file](https://opentelemetry.io/docs/instrumentation/js/getting-started/nodejs/#setup), configure the following: + +```js +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); +const { AWSXRayPropagator } = require('@opentelemetry/propagator-aws-xray'); +// ... + +const provider = new NodeTracerProvider(); + +// Set the global trace context propagator to use X-Ray formatted trace header +provider.register({ + propagator: new AWSXRayPropagator() +}); +``` + +## Propagator Details + +Example header:`X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1` + +The header consists of three parts: the root trace ID, the parent ID and the sampling decision. + +### Root - The AWS X-Ray format trace ID + +* Format: (spec-version)-(timestamp)-(UUID) + * spec_version - The version of the AWS X-Ray header format. Currently, only "1" is valid. + * timestamp - 32-bit number in base16 format, corresponds to the first 8 characters of the OpenTelemetry trace ID. Note, while X-Ray calls this timestamp, for the purpose of propagation it is opaque and any value will work. + * UUID - 96-bit random number in base16 format, corresponds to the last 10 characters of the OpenTelemetry trace ID. + +Root is analogous to the [OpenTelemetry Trace ID](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/overview.md#spancontext), with some small format changes. +For additional reading, see the [AWS X-Ray Trace ID](https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids) public documentation. + +### Parent - The ID of the AWS X-Ray Segment + +* 64-bit random number in base16 format. Populated from the [OpenTelemetry Span ID](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/overview.md#spancontext). + +### Sampled - The sampling decision + +* Defined in the AWS X-Ray specification as a tri-state field, with "0", "1" and "?" as valid values. Only "0" and "1" are used in this propagator. If "?", a new trace will be started. +* Populated from the [OpenTelemetry trace flags](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/overview.md#spancontext). + +## Useful links + +* For more information on OpenTelemetry, visit: +* For more about OpenTelemetry JavaScript: +* For help or feedback on this project, join us in [GitHub Discussions][discussions-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions +[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@opentelemetry/propagator-aws-xray +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fpropagator-aws-xray.svg diff --git a/packages/propagator-aws-xray/karma.conf.js b/packages/propagator-aws-xray/karma.conf.js new file mode 100644 index 0000000000..edcd9f055f --- /dev/null +++ b/packages/propagator-aws-xray/karma.conf.js @@ -0,0 +1,24 @@ +/*! + * Copyright 2020, 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 + * + * http://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. + */ + +const karmaWebpackConfig = require('../../karma.webpack'); +const karmaBaseConfig = require('../../karma.base'); + +module.exports = (config) => { + config.set(Object.assign({}, karmaBaseConfig, { + webpack: karmaWebpackConfig + })) +}; diff --git a/packages/propagator-aws-xray/package.json b/packages/propagator-aws-xray/package.json new file mode 100644 index 0000000000..e60e694eec --- /dev/null +++ b/packages/propagator-aws-xray/package.json @@ -0,0 +1,85 @@ +{ + "name": "@opentelemetry/propagator-aws-xray", + "version": "1.23.0", + "description": "OpenTelemetry AWS Xray propagator provides context propagation for systems that are using AWS X-Ray format.", + "main": "build/src/index.js", + "module": "build/esm/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js", + "scripts": { + "prepublishOnly": "npm run compile", + "compile": "tsc --build tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/index-webpack.ts'", + "test:browser": "karma start --single-run", + "tdd": "npm run tdd:node", + "tdd:node": "npm run test -- --watch-extensions ts --watch", + "tdd:browser": "karma start", + "codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", + "codecov:browser": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", + "clean": "tsc --build --clean tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "version": "node ../../scripts/version-update.js", + "watch": "tsc --build --watch tsconfig.json tsconfig.esm.json tsconfig.esnext.json", + "precompile": "cross-var lerna run version --scope $npm_package_name --include-dependencies", + "prewatch": "npm run precompile", + "peer-api-check": "node ../../scripts/peer-api-check.js" + }, + "keywords": [ + "opentelemetry", + "nodejs", + "tracing", + "profiling", + "awsxray" + ], + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "files": [ + "build/esm/**/*.js", + "build/esm/**/*.js.map", + "build/esm/**/*.d.ts", + "build/esnext/**/*.js", + "build/esnext/**/*.js.map", + "build/esnext/**/*.d.ts", + "build/src/**/*.js", + "build/src/**/*.js.map", + "build/src/**/*.d.ts", + "LICENSE", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0" + }, + "devDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0", + "@types/mocha": "10.0.6", + "@types/node": "18.6.5", + "@types/webpack-env": "1.16.3", + "babel-plugin-istanbul": "6.1.1", + "codecov": "3.8.3", + "cross-var": "1.1.0", + "karma": "6.4.2", + "karma-chrome-launcher": "3.1.0", + "karma-coverage": "2.2.1", + "karma-mocha": "2.0.1", + "karma-spec-reporter": "0.0.36", + "karma-webpack": "4.0.2", + "lerna": "6.6.2", + "mocha": "10.2.0", + "nyc": "15.1.0", + "ts-loader": "8.4.0", + "ts-mocha": "10.0.0", + "typescript": "4.4.4", + "webpack": "5.89.0" + }, + "dependencies": { + "@opentelemetry/core": "1.23.0" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/propagator-aws-xray#readme" +} diff --git a/packages/propagator-aws-xray/src/AWSXRayPropagator.ts b/packages/propagator-aws-xray/src/AWSXRayPropagator.ts new file mode 100644 index 0000000000..e06568d9ee --- /dev/null +++ b/packages/propagator-aws-xray/src/AWSXRayPropagator.ts @@ -0,0 +1,200 @@ +/* + * 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 { + trace, + Context, + TextMapPropagator, + SpanContext, + TraceFlags, + TextMapSetter, + TextMapGetter, + isSpanContextValid, + isValidSpanId, + isValidTraceId, + INVALID_TRACEID, + INVALID_SPANID, + INVALID_SPAN_CONTEXT, +} from '@opentelemetry/api'; + +export const AWSXRAY_TRACE_ID_HEADER = 'x-amzn-trace-id'; + +const TRACE_HEADER_DELIMITER = ';'; +const KV_DELIMITER = '='; + +const TRACE_ID_KEY = 'Root'; +const TRACE_ID_LENGTH = 35; +const TRACE_ID_VERSION = '1'; +const TRACE_ID_DELIMITER = '-'; +const TRACE_ID_DELIMITER_INDEX_1 = 1; +const TRACE_ID_DELIMITER_INDEX_2 = 10; +const TRACE_ID_FIRST_PART_LENGTH = 8; + +const PARENT_ID_KEY = 'Parent'; + +const SAMPLED_FLAG_KEY = 'Sampled'; +const IS_SAMPLED = '1'; +const NOT_SAMPLED = '0'; + +/** + * Implementation of the AWS X-Ray Trace Header propagation protocol. See AWS + * Tracing header spec + * + * An example AWS Xray Tracing Header is shown below: + * X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1 + */ +export class AWSXRayPropagator implements TextMapPropagator { + inject(context: Context, carrier: unknown, setter: TextMapSetter) { + const spanContext = trace.getSpan(context)?.spanContext(); + if (!spanContext || !isSpanContextValid(spanContext)) return; + + const otTraceId = spanContext.traceId; + const timestamp = otTraceId.substring(0, TRACE_ID_FIRST_PART_LENGTH); + const randomNumber = otTraceId.substring(TRACE_ID_FIRST_PART_LENGTH); + + const parentId = spanContext.spanId; + const samplingFlag = + (TraceFlags.SAMPLED & spanContext.traceFlags) === TraceFlags.SAMPLED + ? IS_SAMPLED + : NOT_SAMPLED; + // TODO: Add OT trace state to the X-Ray trace header + + const traceHeader = `Root=1-${timestamp}-${randomNumber};Parent=${parentId};Sampled=${samplingFlag}`; + setter.set(carrier, AWSXRAY_TRACE_ID_HEADER, traceHeader); + } + + extract(context: Context, carrier: unknown, getter: TextMapGetter): Context { + const spanContext = this.getSpanContextFromHeader(carrier, getter); + if (!isSpanContextValid(spanContext)) return context; + + return trace.setSpan(context, trace.wrapSpanContext(spanContext)); + } + + fields(): string[] { + return [AWSXRAY_TRACE_ID_HEADER]; + } + + private getSpanContextFromHeader( + carrier: unknown, + getter: TextMapGetter + ): SpanContext { + const headerKeys = getter.keys(carrier); + const relevantHeaderKey = headerKeys.find(e => { + return e.toLowerCase() === AWSXRAY_TRACE_ID_HEADER; + }); + if (!relevantHeaderKey) { + return INVALID_SPAN_CONTEXT; + } + const traceHeader = getter.get(carrier, relevantHeaderKey); + + if (!traceHeader || typeof traceHeader !== 'string') { + return INVALID_SPAN_CONTEXT; + } + + let pos = 0; + let trimmedPart: string; + let parsedTraceId = INVALID_TRACEID; + let parsedSpanId = INVALID_SPANID; + let parsedTraceFlags = null; + while (pos < traceHeader.length) { + const delimiterIndex = traceHeader.indexOf(TRACE_HEADER_DELIMITER, pos); + if (delimiterIndex >= 0) { + trimmedPart = traceHeader.substring(pos, delimiterIndex).trim(); + pos = delimiterIndex + 1; + } else { + //last part + trimmedPart = traceHeader.substring(pos).trim(); + pos = traceHeader.length; + } + const equalsIndex = trimmedPart.indexOf(KV_DELIMITER); + + const value = trimmedPart.substring(equalsIndex + 1); + + if (trimmedPart.startsWith(TRACE_ID_KEY)) { + parsedTraceId = AWSXRayPropagator._parseTraceId(value); + } else if (trimmedPart.startsWith(PARENT_ID_KEY)) { + parsedSpanId = AWSXRayPropagator._parseSpanId(value); + } else if (trimmedPart.startsWith(SAMPLED_FLAG_KEY)) { + parsedTraceFlags = AWSXRayPropagator._parseTraceFlag(value); + } + } + if (parsedTraceFlags === null) { + return INVALID_SPAN_CONTEXT; + } + const resultSpanContext: SpanContext = { + traceId: parsedTraceId, + spanId: parsedSpanId, + traceFlags: parsedTraceFlags, + isRemote: true, + }; + if (!isSpanContextValid(resultSpanContext)) { + return INVALID_SPAN_CONTEXT; + } + return resultSpanContext; + } + + private static _parseTraceId(xrayTraceId: string): string { + // Check length of trace id + if (xrayTraceId.length !== TRACE_ID_LENGTH) { + return INVALID_TRACEID; + } + + // Check version trace id version + if (!xrayTraceId.startsWith(TRACE_ID_VERSION)) { + return INVALID_TRACEID; + } + + // Check delimiters + if ( + xrayTraceId.charAt(TRACE_ID_DELIMITER_INDEX_1) !== TRACE_ID_DELIMITER || + xrayTraceId.charAt(TRACE_ID_DELIMITER_INDEX_2) !== TRACE_ID_DELIMITER + ) { + return INVALID_TRACEID; + } + + const epochPart = xrayTraceId.substring( + TRACE_ID_DELIMITER_INDEX_1 + 1, + TRACE_ID_DELIMITER_INDEX_2 + ); + const uniquePart = xrayTraceId.substring( + TRACE_ID_DELIMITER_INDEX_2 + 1, + TRACE_ID_LENGTH + ); + const resTraceId = epochPart + uniquePart; + + // Check the content of trace id + if (!isValidTraceId(resTraceId)) { + return INVALID_TRACEID; + } + + return resTraceId; + } + + private static _parseSpanId(xrayParentId: string): string { + return isValidSpanId(xrayParentId) ? xrayParentId : INVALID_SPANID; + } + + private static _parseTraceFlag(xraySampledFlag: string): TraceFlags | null { + if (xraySampledFlag === NOT_SAMPLED) { + return TraceFlags.NONE; + } + if (xraySampledFlag === IS_SAMPLED) { + return TraceFlags.SAMPLED; + } + return null; + } +} diff --git a/packages/propagator-aws-xray/src/index.ts b/packages/propagator-aws-xray/src/index.ts new file mode 100644 index 0000000000..dea8476683 --- /dev/null +++ b/packages/propagator-aws-xray/src/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './AWSXRayPropagator'; diff --git a/packages/propagator-aws-xray/test/AWSXRayPropagator.test.ts b/packages/propagator-aws-xray/test/AWSXRayPropagator.test.ts new file mode 100644 index 0000000000..5cf47916d5 --- /dev/null +++ b/packages/propagator-aws-xray/test/AWSXRayPropagator.test.ts @@ -0,0 +1,328 @@ +/* + * 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 * as assert from 'assert'; + +import { + defaultTextMapGetter, + defaultTextMapSetter, + INVALID_SPAN_CONTEXT, + ROOT_CONTEXT, + SpanContext, + TraceFlags, + trace, +} from '@opentelemetry/api'; +import { TraceState } from '@opentelemetry/core'; + +import { AWSXRAY_TRACE_ID_HEADER, AWSXRayPropagator } from '../src'; + +describe('AWSXRayPropagator', () => { + const xrayPropagator = new AWSXRayPropagator(); + const TRACE_ID = '8a3c60f7d188f8fa79d48a391a778fa6'; + const SPAN_ID = '53995c3f42cd8ad8'; + const SAMPLED_TRACE_FLAG = TraceFlags.SAMPLED; + const NOT_SAMPLED_TRACE_FLAG = TraceFlags.NONE; + + let carrier: { [key: string]: unknown }; + + beforeEach(() => { + carrier = {}; + }); + + describe('.inject()', () => { + it('should inject sampled context', () => { + const spanContext: SpanContext = { + traceId: TRACE_ID, + spanId: SPAN_ID, + traceFlags: SAMPLED_TRACE_FLAG, + }; + xrayPropagator.inject( + trace.setSpan(ROOT_CONTEXT, trace.wrapSpanContext(spanContext)), + carrier, + defaultTextMapSetter + ); + + assert.deepStrictEqual( + carrier[AWSXRAY_TRACE_ID_HEADER], + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1' + ); + }); + + it('should inject not sampled context', () => { + const spanContext: SpanContext = { + traceId: TRACE_ID, + spanId: SPAN_ID, + traceFlags: NOT_SAMPLED_TRACE_FLAG, + }; + xrayPropagator.inject( + trace.setSpan(ROOT_CONTEXT, trace.wrapSpanContext(spanContext)), + carrier, + defaultTextMapSetter + ); + + assert.deepStrictEqual( + carrier[AWSXRAY_TRACE_ID_HEADER], + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=0' + ); + }); + + it('should inject with TraceState', () => { + const traceState = new TraceState(); + traceState.set('foo', 'bar'); + const spanContext: SpanContext = { + traceId: TRACE_ID, + spanId: SPAN_ID, + traceFlags: SAMPLED_TRACE_FLAG, + traceState: traceState, + }; + xrayPropagator.inject( + trace.setSpan(ROOT_CONTEXT, trace.wrapSpanContext(spanContext)), + carrier, + defaultTextMapSetter + ); + + // TODO: assert trace state when the propagator supports it + assert.deepStrictEqual( + carrier[AWSXRAY_TRACE_ID_HEADER], + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1' + ); + }); + + it('inject without spanContext - should inject nothing', () => { + xrayPropagator.inject(ROOT_CONTEXT, carrier, defaultTextMapSetter); + + assert.deepStrictEqual(carrier, {}); + }); + + it('inject default invalid spanContext - should inject nothing', () => { + xrayPropagator.inject( + trace.setSpan( + ROOT_CONTEXT, + trace.wrapSpanContext(INVALID_SPAN_CONTEXT) + ), + carrier, + defaultTextMapSetter + ); + + assert.deepStrictEqual(carrier, {}); + }); + }); + + describe('.extract()', () => { + it('extract nothing from context', () => { + // context remains untouched + assert.strictEqual( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter), + ROOT_CONTEXT + ); + }); + + it('should extract sampled context', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, { + traceId: TRACE_ID, + spanId: SPAN_ID, + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + it('should extract sampled context with arbitrary order', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Parent=53995c3f42cd8ad8;Sampled=1;Root=1-8a3c60f7-d188f8fa79d48a391a778fa6'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, { + traceId: TRACE_ID, + spanId: SPAN_ID, + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + it('should extract context with additional fields', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Foo=Bar'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + // TODO: assert additional fields when the propagator supports it + assert.deepStrictEqual(extractedSpanContext, { + traceId: TRACE_ID, + spanId: SPAN_ID, + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + it('extract empty header value - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = ''; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid traceId - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-abcdefgh-ijklmnopabcdefghijklmnop;Parent=53995c3f42cd8ad8;Sampled=0'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid traceId size - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa600;Parent=53995c3f42cd8ad8;Sampled=0'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid traceId delimiter - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1*8a3c60f7+d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Foo=Bar'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid spanId - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=abcdefghijklmnop;Sampled=0'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid spanId size - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad800;Sampled=0'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid traceFlags - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled='; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid traceFlags length - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=10220'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract nonnumeric invalid traceFlags - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=a'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extract invalid aws xray version - should return undefined', () => { + carrier[AWSXRAY_TRACE_ID_HEADER] = + 'Root=2-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, undefined); + }); + + it('extracts context in a case-insensitive fashion', () => { + carrier[AWSXRAY_TRACE_ID_HEADER.toUpperCase()] = + 'Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=53995c3f42cd8ad8;Sampled=1;Foo=Bar'; + const extractedSpanContext = trace + .getSpan( + xrayPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) + ) + ?.spanContext(); + + assert.deepStrictEqual(extractedSpanContext, { + traceId: TRACE_ID, + spanId: SPAN_ID, + isRemote: true, + traceFlags: TraceFlags.SAMPLED, + }); + }); + + describe('.fields()', () => { + it('should return a field with AWS X-Ray Trace ID header', () => { + const expectedField = xrayPropagator.fields(); + + assert.deepStrictEqual([AWSXRAY_TRACE_ID_HEADER], expectedField); + }); + }); + }); +}); diff --git a/packages/propagator-aws-xray/test/index-webpack.ts b/packages/propagator-aws-xray/test/index-webpack.ts new file mode 100644 index 0000000000..061a48ccfa --- /dev/null +++ b/packages/propagator-aws-xray/test/index-webpack.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ +const testsContext = require.context('.', true, /test$/); +testsContext.keys().forEach(testsContext); + +const srcContext = require.context('.', true, /src$/); +srcContext.keys().forEach(srcContext); diff --git a/packages/propagator-aws-xray/tsconfig.esm.json b/packages/propagator-aws-xray/tsconfig.esm.json new file mode 100644 index 0000000000..76f5aed507 --- /dev/null +++ b/packages/propagator-aws-xray/tsconfig.esm.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.esm.json", + "compilerOptions": { + "outDir": "build/esm", + "rootDir": "src", + "tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo" + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { + "path": "../../api" + }, + { + "path": "../opentelemetry-core" + } + ] +} diff --git a/packages/propagator-aws-xray/tsconfig.esnext.json b/packages/propagator-aws-xray/tsconfig.esnext.json new file mode 100644 index 0000000000..4b926c1c87 --- /dev/null +++ b/packages/propagator-aws-xray/tsconfig.esnext.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.esnext.json", + "compilerOptions": { + "outDir": "build/esnext", + "rootDir": "src", + "tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo" + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { + "path": "../../api" + }, + { + "path": "../opentelemetry-core" + } + ] +} diff --git a/packages/propagator-aws-xray/tsconfig.json b/packages/propagator-aws-xray/tsconfig.json new file mode 100644 index 0000000000..b9bcaf0434 --- /dev/null +++ b/packages/propagator-aws-xray/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "build", + "rootDir": "." + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ], + "references": [ + { + "path": "../../api" + }, + { + "path": "../opentelemetry-core" + } + ] +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 29491b46ef..709dfba27e 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -83,6 +83,9 @@ { "path": "packages/opentelemetry-semantic-conventions/tsconfig.esm.json" }, + { + "path": "packages/propagator-aws-xray/tsconfig.esm.json" + }, { "path": "packages/sdk-metrics/tsconfig.esm.json" } diff --git a/tsconfig.esnext.json b/tsconfig.esnext.json index 031a2e5609..98ff495aa5 100644 --- a/tsconfig.esnext.json +++ b/tsconfig.esnext.json @@ -83,6 +83,9 @@ { "path": "packages/opentelemetry-semantic-conventions/tsconfig.esnext.json" }, + { + "path": "packages/propagator-aws-xray/tsconfig.esnext.json" + }, { "path": "packages/sdk-metrics/tsconfig.esnext.json" } diff --git a/tsconfig.json b/tsconfig.json index acf7329087..260dccb6f6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -44,6 +44,7 @@ "packages/opentelemetry-sdk-trace-web", "packages/opentelemetry-semantic-conventions", "packages/opentelemetry-shim-opentracing", + "packages/propagator-aws-xray", "packages/sdk-metrics" ], "out": "docs", @@ -177,6 +178,9 @@ { "path": "packages/opentelemetry-shim-opentracing" }, + { + "path": "packages/propagator-aws-xray" + }, { "path": "packages/sdk-metrics" },