Skip to content

Commit 6dcde28

Browse files
committed
feat(sdk-node): add serviceInstanceIDDetector to NodeSDK
Follow up from #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`
1 parent 670d528 commit 6dcde28

File tree

8 files changed

+167
-21
lines changed

8 files changed

+167
-21
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/
1313

1414
* feat(sdk-trace-base): log resource attributes in ConsoleSpanExporter [#4605](https://github.com/open-telemetry/opentelemetry-js/pull/4605) @pichlermarc
1515
* feat(resources): new detector ServiceInstanceIDDetector that sets the value for `service.instance.id` as random UUID.
16+
* feat(resources): add usage for the detector ServiceInstanceIDDetector.
17+
* The resource detector can be added to default resource detector list by
18+
* setting the environment variable `OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID` as `true`
19+
* 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`
20+
* The value can be overwritten by
21+
* merging a resource containing the `service.instance.id` attribute
22+
* setting `service.instance.id` via the `OTEL_RESOURCE_ATTRIBUTES` environment variable when using `envDetector`
23+
* using another resource detector which writes `service.instance.id`
1624

1725
### :bug: (Bug Fix)
1826

experimental/packages/opentelemetry-sdk-node/src/sdk.ts

+17-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
processDetector,
3737
Resource,
3838
ResourceDetectionConfig,
39+
serviceInstanceIDDetector,
3940
} from '@opentelemetry/resources';
4041
import { LogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';
4142
import { MeterProvider, MetricReader, View } from '@opentelemetry/sdk-metrics';
@@ -51,7 +52,10 @@ import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
5152
import { NodeSDKConfiguration } from './types';
5253
import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter';
5354
import { getEnv, getEnvWithoutDefaults } from '@opentelemetry/core';
54-
import { parseInstrumentationOptions } from './utils';
55+
import {
56+
getResourceDetectorsFromEnv,
57+
parseInstrumentationOptions,
58+
} from './utils';
5559

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

@@ -121,11 +125,18 @@ export class NodeSDK {
121125
this._configuration = configuration;
122126

123127
this._resource = configuration.resource ?? new Resource({});
124-
this._resourceDetectors = configuration.resourceDetectors ?? [
125-
envDetector,
126-
processDetector,
127-
hostDetector,
128-
];
128+
let defaultDetectors: (Detector | DetectorSync)[] = [];
129+
if (env.OTEL_NODE_RESOURCE_DETECTORS.length > 0) {
130+
defaultDetectors = getResourceDetectorsFromEnv();
131+
} else {
132+
defaultDetectors = [envDetector, processDetector, hostDetector];
133+
if (env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID) {
134+
defaultDetectors.push(serviceInstanceIDDetector);
135+
}
136+
}
137+
138+
this._resourceDetectors =
139+
configuration.resourceDetectors ?? defaultDetectors;
129140

130141
this._serviceName = configuration.serviceName;
131142

experimental/packages/opentelemetry-sdk-node/src/utils.ts

+50
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { diag } from '@opentelemetry/api';
1718
import {
1819
Instrumentation,
1920
InstrumentationOption,
2021
} from '@opentelemetry/instrumentation';
22+
import {
23+
Detector,
24+
DetectorSync,
25+
envDetectorSync,
26+
hostDetectorSync,
27+
osDetectorSync,
28+
processDetectorSync,
29+
serviceInstanceIDDetectorSync,
30+
} from '@opentelemetry/resources';
2131

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

4252
return instrumentations;
4353
}
54+
55+
const RESOURCE_DETECTOR_ENVIRONMENT = 'env';
56+
const RESOURCE_DETECTOR_HOST = 'host';
57+
const RESOURCE_DETECTOR_OS = 'os';
58+
const RESOURCE_DETECTOR_PROCESS = 'process';
59+
const RESOURCE_DETECTOR_SERVICE_INSTANCE_ID = 'serviceinstance';
60+
61+
export function getResourceDetectorsFromEnv(): Array<Detector | DetectorSync> {
62+
const resourceDetectors = new Map<
63+
string,
64+
Detector | DetectorSync | Detector[]
65+
>([
66+
[RESOURCE_DETECTOR_ENVIRONMENT, envDetectorSync],
67+
[RESOURCE_DETECTOR_HOST, hostDetectorSync],
68+
[RESOURCE_DETECTOR_OS, osDetectorSync],
69+
[RESOURCE_DETECTOR_SERVICE_INSTANCE_ID, serviceInstanceIDDetectorSync],
70+
[RESOURCE_DETECTOR_PROCESS, processDetectorSync],
71+
]);
72+
73+
const resourceDetectorsFromEnv =
74+
process.env.OTEL_NODE_RESOURCE_DETECTORS?.split(',') ?? ['all'];
75+
76+
if (resourceDetectorsFromEnv.includes('all')) {
77+
return [...resourceDetectors.values()].flat();
78+
}
79+
80+
if (resourceDetectorsFromEnv.includes('none')) {
81+
return [];
82+
}
83+
84+
return resourceDetectorsFromEnv.flatMap(detector => {
85+
const resourceDetector = resourceDetectors.get(detector);
86+
if (!resourceDetector) {
87+
diag.error(
88+
`Invalid resource detector "${detector}" specified in the environment variable OTEL_NODE_RESOURCE_DETECTORS`
89+
);
90+
}
91+
return resourceDetector || [];
92+
});
93+
}

experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts

+69-12
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ import {
3939
View,
4040
} from '@opentelemetry/sdk-metrics';
4141
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
42-
import { assertServiceResource } from './util/resource-assertions';
42+
import {
43+
assertServiceInstanceIDIsUUID,
44+
assertServiceResource,
45+
} from './util/resource-assertions';
4346
import {
4447
ConsoleSpanExporter,
4548
SimpleSpanProcessor,
@@ -71,7 +74,6 @@ import {
7174
import {
7275
SEMRESATTRS_HOST_NAME,
7376
SEMRESATTRS_PROCESS_PID,
74-
SEMRESATTRS_SERVICE_INSTANCE_ID,
7577
} from '@opentelemetry/semantic-conventions';
7678

7779
const DefaultContextManager = semver.gte(process.version, '14.8.0')
@@ -682,7 +684,7 @@ describe('Node SDK', () => {
682684
describe('configureServiceInstanceId', async () => {
683685
it('should configure service instance id via OTEL_RESOURCE_ATTRIBUTES env var', async () => {
684686
process.env.OTEL_RESOURCE_ATTRIBUTES =
685-
'service.instance.id=627cc493,service.name=my-service';
687+
'service.instance.id=627cc493,service.name=my-service,service.namespace';
686688
const sdk = new NodeSDK();
687689

688690
sdk.start();
@@ -694,7 +696,20 @@ describe('Node SDK', () => {
694696
instanceId: '627cc493',
695697
});
696698
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
697-
sdk.shutdown();
699+
await sdk.shutdown();
700+
});
701+
702+
it('should configure service instance id via OTEL_NODE_RESOURCE_DETECTORS env var', async () => {
703+
process.env.OTEL_NODE_RESOURCE_DETECTORS = 'env,host,os,serviceinstance';
704+
const sdk = new NodeSDK();
705+
706+
sdk.start();
707+
const resource = sdk['_resource'];
708+
await resource.waitForAsyncAttributes?.();
709+
710+
assertServiceInstanceIDIsUUID(resource);
711+
delete process.env.OTEL_NODE_RESOURCE_DETECTORS;
712+
await sdk.shutdown();
698713
});
699714

700715
it('should configure service instance id with random UUID', async () => {
@@ -712,14 +727,56 @@ describe('Node SDK', () => {
712727
const resource = sdk['_resource'];
713728
await resource.waitForAsyncAttributes?.();
714729

715-
const UUID_REGEX =
716-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
717-
assert.equal(
718-
UUID_REGEX.test(
719-
resource.attributes[SEMRESATTRS_SERVICE_INSTANCE_ID]?.toString() || ''
720-
),
721-
true
722-
);
730+
assertServiceInstanceIDIsUUID(resource);
731+
delete process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID;
732+
await sdk.shutdown();
733+
});
734+
735+
it('should configure service instance id with random UUID with OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID env var', async () => {
736+
process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID = 'true';
737+
const sdk = new NodeSDK();
738+
739+
sdk.start();
740+
const resource = sdk['_resource'];
741+
await resource.waitForAsyncAttributes?.();
742+
743+
assertServiceInstanceIDIsUUID(resource);
744+
delete process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID;
745+
await sdk.shutdown();
746+
});
747+
748+
it('should configure service instance id via OTEL_RESOURCE_ATTRIBUTES env var even with OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID env var', async () => {
749+
process.env.OTEL_RESOURCE_ATTRIBUTES =
750+
'service.instance.id=627cc493,service.name=my-service';
751+
process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID = 'true';
752+
const sdk = new NodeSDK();
753+
754+
sdk.start();
755+
const resource = sdk['_resource'];
756+
await resource.waitForAsyncAttributes?.();
757+
758+
assertServiceResource(resource, {
759+
name: 'my-service',
760+
instanceId: '627cc493',
761+
});
762+
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
763+
delete process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID;
764+
sdk.shutdown();
765+
});
766+
767+
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 () => {
768+
process.env.OTEL_RESOURCE_ATTRIBUTES = 'service.name=my-service';
769+
process.env.OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID = 'false';
770+
const sdk = new NodeSDK();
771+
772+
sdk.start();
773+
const resource = sdk['_resource'];
774+
await resource.waitForAsyncAttributes?.();
775+
776+
assertServiceResource(resource, {
777+
name: 'my-service',
778+
});
779+
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
723780
await sdk.shutdown();
724781
});
725782
});

experimental/packages/opentelemetry-sdk-node/test/util/resource-assertions.ts

+12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { SDK_INFO } from '@opentelemetry/core';
1818
import * as assert from 'assert';
1919
import { IResource, Resource } from '@opentelemetry/resources';
2020
import {
21+
SEMRESATTRS_SERVICE_INSTANCE_ID,
2122
SEMRESATTRS_TELEMETRY_SDK_LANGUAGE,
2223
SEMRESATTRS_TELEMETRY_SDK_NAME,
2324
SEMRESATTRS_TELEMETRY_SDK_VERSION,
@@ -336,3 +337,14 @@ const assertHasOneLabel = (prefix: string, resource: Resource): void => {
336337
JSON.stringify(Object.keys(SemanticResourceAttributes))
337338
);
338339
};
340+
341+
export const assertServiceInstanceIDIsUUID = (resource: Resource): void => {
342+
const UUID_REGEX =
343+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
344+
assert.equal(
345+
UUID_REGEX.test(
346+
resource.attributes[SEMRESATTRS_SERVICE_INSTANCE_ID]?.toString() || ''
347+
),
348+
true
349+
);
350+
};

packages/opentelemetry-core/src/utils/environment.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ const DEFAULT_LIST_SEPARATOR = ',';
2424
* Environment interface to define all names
2525
*/
2626

27-
const ENVIRONMENT_BOOLEAN_KEYS = ['OTEL_SDK_DISABLED'] as const;
27+
const ENVIRONMENT_BOOLEAN_KEYS = [
28+
'OTEL_SDK_DISABLED',
29+
'OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID',
30+
] as const;
2831

2932
type ENVIRONMENT_BOOLEANS = {
3033
[K in (typeof ENVIRONMENT_BOOLEAN_KEYS)[number]]?: boolean;
@@ -75,6 +78,7 @@ function isEnvVarANumber(key: unknown): key is keyof ENVIRONMENT_NUMBERS {
7578
const ENVIRONMENT_LISTS_KEYS = [
7679
'OTEL_NO_PATCH_MODULES',
7780
'OTEL_PROPAGATORS',
81+
'OTEL_NODE_RESOURCE_DETECTORS',
7882
] as const;
7983

8084
type ENVIRONMENT_LISTS = {
@@ -192,6 +196,7 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
192196
OTEL_LOG_LEVEL: DiagLogLevel.INFO,
193197
OTEL_NO_PATCH_MODULES: [],
194198
OTEL_PROPAGATORS: ['tracecontext', 'baggage'],
199+
OTEL_NODE_RESOURCE_DETECTORS: [],
195200
OTEL_RESOURCE_ATTRIBUTES: '',
196201
OTEL_SERVICE_NAME: '',
197202
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT,
@@ -236,6 +241,7 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
236241
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: 'http/protobuf',
237242
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: 'http/protobuf',
238243
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: 'cumulative',
244+
OTEL_NODE_EXPERIMENTAL_DEFAULT_SERVICE_INSTANCE_ID: false,
239245
};
240246

241247
/**

packages/opentelemetry-resources/src/platform/browser/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
export * from './default-service-name';
1818
export * from './HostDetector';
19-
export * from './OSDetector';
2019
export * from './HostDetectorSync';
20+
export * from './OSDetector';
2121
export * from './OSDetectorSync';
2222
export * from './ProcessDetector';
2323
export * from './ProcessDetectorSync';
2424
export * from './ServiceInstanceIDDetector';
25+
export * from './ServiceInstanceIDDetectorSync';

packages/opentelemetry-resources/src/platform/node/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
export * from './default-service-name';
1818
export * from './HostDetector';
19-
export * from './OSDetector';
2019
export * from './HostDetectorSync';
20+
export * from './OSDetector';
2121
export * from './OSDetectorSync';
2222
export * from './ProcessDetector';
2323
export * from './ProcessDetectorSync';
2424
export * from './ServiceInstanceIDDetector';
25+
export * from './ServiceInstanceIDDetectorSync';

0 commit comments

Comments
 (0)