Skip to content

Commit

Permalink
feat(sdk-node): add serviceInstanceIDDetector to NodeSDK
Browse files Browse the repository at this point in the history
Follow up from open-telemetry#4608

Adds the resource detector ServiceInstanceIDDetector on the NodeSDK constructor.
It only gets added by default on any of those conditions:
- the value `serviceinstance` is part of the list `OTEL_NODE_RESOURCE_DETECTORS`
- `OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID` is set to `true`
  • Loading branch information
maryliag committed Apr 10, 2024
1 parent 670d528 commit 6dcde28
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 21 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/

* feat(sdk-trace-base): log resource attributes in ConsoleSpanExporter [#4605](https://github.com/open-telemetry/opentelemetry-js/pull/4605) @pichlermarc
* feat(resources): new detector ServiceInstanceIDDetector that sets the value for `service.instance.id` as random UUID.
* feat(resources): add usage for the detector ServiceInstanceIDDetector.
* The resource detector can be added to default resource detector list by
* setting the environment variable `OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID` as `true`
* adding the value `serviceinstance` to the list of resource detectors on the environment variable `OTEL_NODE_RESOURCE_DETECTORS`, e.g `OTEL_NODE_RESOURCE_DETECTORS=env,host,os,serviceinstance`
* The value can be overwritten by
* merging a resource containing the `service.instance.id` attribute
* setting `service.instance.id` via the `OTEL_RESOURCE_ATTRIBUTES` environment variable when using `envDetector`
* using another resource detector which writes `service.instance.id`

### :bug: (Bug Fix)

Expand Down
23 changes: 17 additions & 6 deletions experimental/packages/opentelemetry-sdk-node/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
processDetector,
Resource,
ResourceDetectionConfig,
serviceInstanceIDDetector,
} from '@opentelemetry/resources';
import { LogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';
import { MeterProvider, MetricReader, View } from '@opentelemetry/sdk-metrics';
Expand All @@ -51,7 +52,10 @@ import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { NodeSDKConfiguration } from './types';
import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter';
import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core';
import { parseInstrumentationOptions } from './utils';
import {
getResourceDetectorsFromEnv,
parseInstrumentationOptions,
} from './utils';

/** This class represents everything needed to register a fully configured OpenTelemetry Node.js SDK */

Expand Down Expand Up @@ -121,11 +125,18 @@ export class NodeSDK {
this._configuration = configuration;

this._resource = configuration.resource ?? new Resource({});
this._resourceDetectors = configuration.resourceDetectors ?? [
envDetector,
processDetector,
hostDetector,
];
let defaultDetectors: (Detector | DetectorSync)[] = [];
if (env.OTEL_NODE_RESOURCE_DETECTORS.length > 0) {
defaultDetectors = getResourceDetectorsFromEnv();
} else {
defaultDetectors = [envDetector, processDetector, hostDetector];
if (env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID) {
defaultDetectors.push(serviceInstanceIDDetector);
}
}

this._resourceDetectors =
configuration.resourceDetectors ?? defaultDetectors;

this._serviceName = configuration.serviceName;

Expand Down
50 changes: 50 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@
* limitations under the License.
*/

import { diag } from '@opentelemetry/api';
import {
Instrumentation,
InstrumentationOption,
} from '@opentelemetry/instrumentation';
import {
Detector,
DetectorSync,
envDetectorSync,
hostDetectorSync,
osDetectorSync,
processDetectorSync,
serviceInstanceIDDetectorSync,
} from '@opentelemetry/resources';

// TODO: This part of a workaround to fix https://github.com/open-telemetry/opentelemetry-js/issues/3609
// If the MeterProvider is not yet registered when instrumentations are registered, all metrics are dropped.
Expand All @@ -41,3 +51,43 @@ export function parseInstrumentationOptions(

return instrumentations;
}

const RESOURCE_DETECTOR_ENVIRONMENT = 'env';
const RESOURCE_DETECTOR_HOST = 'host';
const RESOURCE_DETECTOR_OS = 'os';
const RESOURCE_DETECTOR_PROCESS = 'process';
const RESOURCE_DETECTOR_SERVICE_INSTANCE_ID = 'serviceinstance';

export function getResourceDetectorsFromEnv(): Array<Detector | DetectorSync> {
const resourceDetectors = new Map<
string,
Detector | DetectorSync | Detector[]
>([
[RESOURCE_DETECTOR_ENVIRONMENT, envDetectorSync],
[RESOURCE_DETECTOR_HOST, hostDetectorSync],
[RESOURCE_DETECTOR_OS, osDetectorSync],
[RESOURCE_DETECTOR_SERVICE_INSTANCE_ID, serviceInstanceIDDetectorSync],
[RESOURCE_DETECTOR_PROCESS, processDetectorSync],
]);

const resourceDetectorsFromEnv =
process.env.OTEL_NODE_RESOURCE_DETECTORS?.split(',') ?? ['all'];

if (resourceDetectorsFromEnv.includes('all')) {
return [...resourceDetectors.values()].flat();
}

if (resourceDetectorsFromEnv.includes('none')) {
return [];
}

return resourceDetectorsFromEnv.flatMap(detector => {
const resourceDetector = resourceDetectors.get(detector);
if (!resourceDetector) {
diag.error(
`Invalid resource detector "${detector}" specified in the environment variable OTEL_NODE_RESOURCE_DETECTORS`
);
}
return resourceDetector || [];
});
}
81 changes: 69 additions & 12 deletions experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import {
View,
} from '@opentelemetry/sdk-metrics';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { assertServiceResource } from './util/resource-assertions';
import {
assertServiceInstanceIDIsUUID,
assertServiceResource,
} from './util/resource-assertions';
import {
ConsoleSpanExporter,
SimpleSpanProcessor,
Expand Down Expand Up @@ -71,7 +74,6 @@ 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')
Expand Down Expand Up @@ -682,7 +684,7 @@ describe('Node SDK', () => {
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';
'service.instance.id=627cc493,service.name=my-service,service.namespace';
const sdk = new NodeSDK();

sdk.start();
Expand All @@ -694,7 +696,20 @@ describe('Node SDK', () => {
instanceId: '627cc493',
});
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
sdk.shutdown();
await sdk.shutdown();
});

it('should configure service instance id via OTEL_NODE_RESOURCE_DETECTORS env var', async () => {
process.env.OTEL_NODE_RESOURCE_DETECTORS = 'env,host,os,serviceinstance';
const sdk = new NodeSDK();

sdk.start();
const resource = sdk['_resource'];
await resource.waitForAsyncAttributes?.();

assertServiceInstanceIDIsUUID(resource);
delete process.env.OTEL_NODE_RESOURCE_DETECTORS;
await sdk.shutdown();
});

it('should configure service instance id with random UUID', async () => {
Expand All @@ -712,14 +727,56 @@ describe('Node SDK', () => {
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
);
assertServiceInstanceIDIsUUID(resource);
delete process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID;
await sdk.shutdown();
});

it('should configure service instance id with random UUID with OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID env var', async () => {
process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID = 'true';
const sdk = new NodeSDK();

sdk.start();
const resource = sdk['_resource'];
await resource.waitForAsyncAttributes?.();

assertServiceInstanceIDIsUUID(resource);
delete process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID;
await sdk.shutdown();
});

it('should configure service instance id via OTEL_RESOURCE_ATTRIBUTES env var even with OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID env var', async () => {
process.env.OTEL_RESOURCE_ATTRIBUTES =
'service.instance.id=627cc493,service.name=my-service';
process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID = 'true';
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;
delete process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID;
sdk.shutdown();
});

it('should not configure service instance id with no value for it on OTEL_RESOURCE_ATTRIBUTES env var and OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID env var as false', async () => {
process.env.OTEL_RESOURCE_ATTRIBUTES = 'service.name=my-service';
process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID = 'false';
const sdk = new NodeSDK();

sdk.start();
const resource = sdk['_resource'];
await resource.waitForAsyncAttributes?.();

assertServiceResource(resource, {
name: 'my-service',
});
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
await sdk.shutdown();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SDK_INFO } from '@opentelemetry/core';
import * as assert from 'assert';
import { IResource, Resource } from '@opentelemetry/resources';
import {
SEMRESATTRS_SERVICE_INSTANCE_ID,
SEMRESATTRS_TELEMETRY_SDK_LANGUAGE,
SEMRESATTRS_TELEMETRY_SDK_NAME,
SEMRESATTRS_TELEMETRY_SDK_VERSION,
Expand Down Expand Up @@ -336,3 +337,14 @@ const assertHasOneLabel = (prefix: string, resource: Resource): void => {
JSON.stringify(Object.keys(SemanticResourceAttributes))
);
};

export const assertServiceInstanceIDIsUUID = (resource: Resource): void => {
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
);
};
8 changes: 7 additions & 1 deletion packages/opentelemetry-core/src/utils/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ const DEFAULT_LIST_SEPARATOR = ',';
* Environment interface to define all names
*/

const ENVIRONMENT_BOOLEAN_KEYS = ['OTEL_SDK_DISABLED'] as const;
const ENVIRONMENT_BOOLEAN_KEYS = [
'OTEL_SDK_DISABLED',
'OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID',
] as const;

type ENVIRONMENT_BOOLEANS = {
[K in (typeof ENVIRONMENT_BOOLEAN_KEYS)[number]]?: boolean;
Expand Down Expand Up @@ -75,6 +78,7 @@ function isEnvVarANumber(key: unknown): key is keyof ENVIRONMENT_NUMBERS {
const ENVIRONMENT_LISTS_KEYS = [
'OTEL_NO_PATCH_MODULES',
'OTEL_PROPAGATORS',
'OTEL_NODE_RESOURCE_DETECTORS',
] as const;

type ENVIRONMENT_LISTS = {
Expand Down Expand Up @@ -192,6 +196,7 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_LOG_LEVEL: DiagLogLevel.INFO,
OTEL_NO_PATCH_MODULES: [],
OTEL_PROPAGATORS: ['tracecontext', 'baggage'],
OTEL_NODE_RESOURCE_DETECTORS: [],
OTEL_RESOURCE_ATTRIBUTES: '',
OTEL_SERVICE_NAME: '',
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT,
Expand Down Expand Up @@ -236,6 +241,7 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: 'http/protobuf',
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: 'http/protobuf',
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: 'cumulative',
OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID: false,
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

export * from './default-service-name';
export * from './HostDetector';
export * from './OSDetector';
export * from './HostDetectorSync';
export * from './OSDetector';
export * from './OSDetectorSync';
export * from './ProcessDetector';
export * from './ProcessDetectorSync';
export * from './ServiceInstanceIDDetector';
export * from './ServiceInstanceIDDetectorSync';
3 changes: 2 additions & 1 deletion packages/opentelemetry-resources/src/platform/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

export * from './default-service-name';
export * from './HostDetector';
export * from './OSDetector';
export * from './HostDetectorSync';
export * from './OSDetector';
export * from './OSDetectorSync';
export * from './ProcessDetector';
export * from './ProcessDetectorSync';
export * from './ServiceInstanceIDDetector';
export * from './ServiceInstanceIDDetectorSync';

0 comments on commit 6dcde28

Please sign in to comment.