diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1ec010f15..286a23dba3a 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 d76534450fd..078a33d9cc7 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 bbae633a5d8..73a66d070e3 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 dfddda8ae1a..d44dbacc4df 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 8572323a09d..329d489ec4d 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 00000000000..d79fa1f4eaf --- /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 9ed1b0151b1..47f5cf301ef 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 00000000000..9196bcba51e --- /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 9ed1b0151b1..47f5cf301ef 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';