Skip to content

Commit c7fbb09

Browse files
committed
refactor: refactor init.ts and environment variable handling
1 parent 887aa71 commit c7fbb09

File tree

3 files changed

+219
-78
lines changed

3 files changed

+219
-78
lines changed

src/init.ts

Lines changed: 108 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ import { version } from '../package.json';
1919
import PodUidDetector from './detectors/node/opentelemetry-resource-detector-kubernetes-pod';
2020
import ServiceNameFallbackDetector from './detectors/node/opentelemetry-resource-detector-service-name-fallback';
2121
import { FileSpanExporter } from './util/FileSpanExporter';
22+
import { hasOptedIn, hasOptedOut, parseNumericEnvironmentVariableWithDefault } from './util/environment';
2223

23-
if (process.env.DASH0_DEBUG) {
24-
console.log('Dash0 OpenTelemetry distribution for Node.js: Starting NodeSDK.');
25-
}
24+
const debugOutput = hasOptedIn('DASH0_DEBUG');
25+
26+
printDebugOutput('Dash0 OpenTelemetry distribution for Node.js: Starting NodeSDK.');
2627

2728
let sdkShutdownHasBeenCalled = false;
2829

@@ -31,98 +32,121 @@ if (process.env.DASH0_OTEL_COLLECTOR_BASE_URL) {
3132
baseUrl = process.env.DASH0_OTEL_COLLECTOR_BASE_URL;
3233
}
3334

34-
const instrumentationConfig: any = {};
35-
if (
36-
!process.env.DASH0_ENABLE_FS_INSTRUMENTATION ||
37-
process.env.DASH0_ENABLE_FS_INSTRUMENTATION.trim().toLowerCase() !== 'true'
38-
) {
39-
instrumentationConfig['@opentelemetry/instrumentation-fs'] = {
40-
enabled: false,
41-
};
42-
}
35+
const configuration: Partial<NodeSDKConfiguration> = {
36+
spanProcessors: spanProcessors(),
37+
metricReader: metricsReader(),
38+
logRecordProcessor: logRecordProcessor(),
39+
instrumentations: [getNodeAutoInstrumentations(createInstrumentationConfig())],
40+
resource: resource(),
41+
resourceDetectors: resourceDetectors(),
42+
};
4343

44-
const spanProcessors: SpanProcessor[] = [
45-
new BatchSpanProcessor(
46-
new OTLPTraceExporter({
47-
url: `${baseUrl}/v1/traces`,
48-
}),
49-
),
50-
];
51-
52-
const logRecordProcessor = new BatchLogRecordProcessor(
53-
new OTLPLogExporter({
54-
url: `${baseUrl}/v1/logs`,
55-
}),
56-
);
57-
58-
if (process.env.DASH0_DEBUG_PRINT_SPANS != null) {
59-
if (process.env.DASH0_DEBUG_PRINT_SPANS.toLocaleLowerCase() === 'true') {
60-
spanProcessors.push(new BatchSpanProcessor(new ConsoleSpanExporter()));
61-
} else {
62-
spanProcessors.push(new BatchSpanProcessor(new FileSpanExporter(process.env.DASH0_DEBUG_PRINT_SPANS)));
44+
const sdk = new NodeSDK(configuration);
45+
46+
sdk.start();
47+
48+
createBootstrapSpanIfRequested();
49+
installProcessExitHandlers();
50+
51+
printDebugOutput('Dash0 OpenTelemetry distribution for Node.js: NodeSDK started.');
52+
53+
function spanProcessors(): SpanProcessor[] {
54+
const spanProcessors: SpanProcessor[] = [
55+
new BatchSpanProcessor(
56+
new OTLPTraceExporter({
57+
url: `${baseUrl}/v1/traces`,
58+
}),
59+
),
60+
];
61+
62+
if (process.env.DASH0_DEBUG_PRINT_SPANS != null) {
63+
if (process.env.DASH0_DEBUG_PRINT_SPANS.toLowerCase() === 'true') {
64+
spanProcessors.push(new BatchSpanProcessor(new ConsoleSpanExporter()));
65+
} else {
66+
spanProcessors.push(new BatchSpanProcessor(new FileSpanExporter(process.env.DASH0_DEBUG_PRINT_SPANS)));
67+
}
6368
}
69+
return spanProcessors;
6470
}
6571

66-
const configuration: Partial<NodeSDKConfiguration> = {
67-
spanProcessors: spanProcessors,
72+
function metricsReader(): PeriodicExportingMetricReader {
73+
// Implement support for config of metric export timeout/interval via environment variables here in the distribution
74+
// until https://github.com/open-telemetry/opentelemetry-js/issues/4655 has been implemented.
75+
// The default values are taken from
76+
// https://github.com/open-telemetry/opentelemetry-js/blob/812c774998fb60a0c666404ae71b1d508e0568f4/packages/sdk-metrics/src/export/PeriodicExportingMetricReader.ts#L97-L98
77+
const exportIntervalMillis = parseNumericEnvironmentVariableWithDefault('OTEL_METRIC_EXPORT_INTERVAL', 60000);
78+
const exportTimeoutMillis = parseNumericEnvironmentVariableWithDefault('OTEL_METRIC_EXPORT_TIMEOUT', 30000);
6879

69-
metricReader: new PeriodicExportingMetricReader({
80+
return new PeriodicExportingMetricReader({
7081
exporter: new OTLPMetricExporter({
7182
url: `${baseUrl}/v1/metrics`,
7283
}),
73-
}),
84+
exportIntervalMillis,
85+
exportTimeoutMillis,
86+
});
87+
}
7488

75-
logRecordProcessor,
89+
function logRecordProcessor() {
90+
return new BatchLogRecordProcessor(
91+
new OTLPLogExporter({
92+
url: `${baseUrl}/v1/logs`,
93+
}),
94+
);
95+
}
7696

77-
instrumentations: [getNodeAutoInstrumentations(instrumentationConfig)],
97+
function createInstrumentationConfig(): any {
98+
const instrumentationConfig: any = {};
99+
if (!hasOptedIn('DASH0_ENABLE_FS_INSTRUMENTATION')) {
100+
instrumentationConfig['@opentelemetry/instrumentation-fs'] = {
101+
enabled: false,
102+
};
103+
}
104+
return instrumentationConfig;
105+
}
78106

79-
resource: new Resource({
107+
function resource() {
108+
return new Resource({
80109
'telemetry.distro.name': 'dash0-nodejs',
81110
'telemetry.distro.version': version,
82-
}),
83-
};
84-
85-
// Copy the behavior of the NodeSDK constructor with regard to resource detectors, but add the pod uid detector.
86-
// https://github.com/open-telemetry/opentelemetry-js/blob/73fddf9b5e7a93bd4cf21c2dbf444cee31d26c88/experimental/packages/opentelemetry-sdk-node/src/sdk.ts#L126-L132
87-
let detectors: (Detector | DetectorSync)[];
88-
if (process.env.OTEL_NODE_RESOURCE_DETECTORS != null) {
89-
detectors = getResourceDetectors();
90-
} else {
91-
detectors = [envDetector, processDetector, containerDetector, hostDetector];
111+
});
92112
}
93-
detectors.push(new PodUidDetector());
94-
detectors.push(new ServiceNameFallbackDetector());
95-
configuration.resourceDetectors = detectors;
96-
97-
const sdk = new NodeSDK(configuration);
98113

99-
sdk.start();
100-
101-
if (process.env.DASH0_BOOTSTRAP_SPAN != null) {
102-
const tracer = trace.getTracer('dash0-nodejs-distribution');
103-
tracer //
104-
.startSpan(process.env.DASH0_BOOTSTRAP_SPAN, {
105-
root: true,
106-
kind: SpanKind.INTERNAL,
107-
})
108-
.end();
114+
function resourceDetectors(): (Detector | DetectorSync)[] {
115+
// Copy the behavior of the NodeSDK constructor with regard to resource detectors, but add the pod uid detector.
116+
// https://github.com/open-telemetry/opentelemetry-js/blob/73fddf9b5e7a93bd4cf21c2dbf444cee31d26c88/experimental/packages/opentelemetry-sdk-node/src/sdk.ts#L126-L132
117+
let detectors: (Detector | DetectorSync)[];
118+
if (process.env.OTEL_NODE_RESOURCE_DETECTORS != null) {
119+
detectors = getResourceDetectors();
120+
} else {
121+
detectors = [envDetector, processDetector, containerDetector, hostDetector];
122+
}
123+
detectors.push(new PodUidDetector());
124+
detectors.push(new ServiceNameFallbackDetector());
125+
return detectors;
109126
}
110127

111-
if (process.env.DASH0_FLUSH_ON_SIGTERM_SIGINT && process.env.DASH0_FLUSH_ON_SIGTERM_SIGINT.toLowerCase() === 'true') {
112-
['SIGTERM', 'SIGINT'].forEach(signal => {
113-
process.once(signal, onProcessExit.bind(null, true));
114-
});
128+
function createBootstrapSpanIfRequested() {
129+
if (process.env.DASH0_BOOTSTRAP_SPAN != null) {
130+
const tracer = trace.getTracer('dash0-nodejs-distribution');
131+
tracer //
132+
.startSpan(process.env.DASH0_BOOTSTRAP_SPAN, {
133+
root: true,
134+
kind: SpanKind.INTERNAL,
135+
})
136+
.end();
137+
}
115138
}
116139

117-
if (
118-
!process.env.DASH0_FLUSH_ON_EMPTY_EVENT_LOOP ||
119-
process.env.DASH0_FLUSH_ON_EMPTY_EVENT_LOOP.toLowerCase() !== 'false'
120-
) {
121-
process.once('beforeExit', onProcessExit.bind(null, false));
122-
}
140+
function installProcessExitHandlers() {
141+
if (hasOptedIn('DASH0_FLUSH_ON_SIGTERM_SIGINT')) {
142+
['SIGTERM', 'SIGINT'].forEach(signal => {
143+
process.once(signal, onProcessExit.bind(null, true));
144+
});
145+
}
123146

124-
if (process.env.DASH0_DEBUG) {
125-
console.log('Dash0 OpenTelemetry distribution for Node.js: NodeSDK started.');
147+
if (!hasOptedOut('DASH0_FLUSH_ON_EMPTY_EVENT_LOOP')) {
148+
process.once('beforeExit', onProcessExit.bind(null, false));
149+
}
126150
}
127151

128152
async function onProcessExit(callProcessExit: boolean) {
@@ -141,9 +165,9 @@ async function gracefulSdkShutdown(callProcessExit: boolean) {
141165
sdkShutdownHasBeenCalled = true;
142166
await sdk.shutdown();
143167

144-
if (process.env.DASH0_DEBUG) {
145-
console.log('Dash0 OpenTelemetry distribution for Node.js: OpenTelemetry SDK has been shut down successfully.');
146-
}
168+
printDebugOutput(
169+
'Dash0 OpenTelemetry distribution for Node.js: OpenTelemetry SDK has been shut down successfully.',
170+
);
147171
} catch (err) {
148172
console.error('Dash0 OpenTelemetry distribution for Node.js: Error shutting down the OpenTelemetry SDK:', err);
149173
} finally {
@@ -174,3 +198,9 @@ function executePromiseWithTimeout(promise: Promise<any>, timeoutMillis: number,
174198
}
175199
});
176200
}
201+
202+
function printDebugOutput(message: string) {
203+
if (debugOutput) {
204+
console.log(message);
205+
}
206+
}

src/util/environment.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-FileCopyrightText: Copyright 2024 Dash0 Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export function hasOptedIn(envVarName: string) {
5+
const raw = process.env[envVarName];
6+
return raw != null && raw.toLowerCase() === 'true';
7+
}
8+
9+
export function hasOptedOut(envVarName: string) {
10+
const raw = process.env[envVarName];
11+
return raw != null && raw.toLowerCase() === 'false';
12+
}
13+
14+
export function parseNumericEnvironmentVariableWithDefault(envVarName: string, defaultValue: number): number {
15+
const raw = process.env[envVarName];
16+
if (!raw) {
17+
return defaultValue;
18+
}
19+
const value = parseInt(raw, 10);
20+
if (isNaN(value)) {
21+
return defaultValue;
22+
}
23+
return value;
24+
}

src/util/environment_test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-FileCopyrightText: Copyright 2024 Dash0 Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { expect } from 'chai';
5+
6+
import { hasOptedIn, hasOptedOut, parseNumericEnvironmentVariableWithDefault } from './environment';
7+
8+
describe('environment variables', () => {
9+
const envVarsUsedInTest = [
10+
//
11+
'DASH0_TEST_IS_NOT_SET',
12+
'DASH0_TEST_IS_SET',
13+
'DASH0_TEST_NOT_A_NUMBER',
14+
'DASH0_TEST_NUMERIC_ENV_VAR',
15+
];
16+
const originalValues: Map<string, string | undefined> = new Map();
17+
18+
before(() => {
19+
envVarsUsedInTest.forEach(envVar => {
20+
originalValues.set(envVar, process.env[envVar]);
21+
});
22+
});
23+
24+
afterEach(() => {
25+
envVarsUsedInTest.forEach(envVar => {
26+
process.env[envVar] = originalValues.get(envVar);
27+
});
28+
});
29+
30+
describe('opt-in', () => {
31+
it('returns false if env var is not set', async () => {
32+
expect(hasOptedIn('DASH0_TEST_IS_NOT_SET')).to.be.false;
33+
});
34+
35+
it('returns false if env var is not set to true', async () => {
36+
process.env.DASH0_TEST_IS_SET = 'whatever';
37+
expect(hasOptedIn('DASH0_TEST_IS_SET')).to.be.false;
38+
});
39+
40+
it('returns true if env var is to true', async () => {
41+
process.env.DASH0_TEST_IS_SET = 'true';
42+
expect(hasOptedIn('DASH0_TEST_IS_SET')).to.be.true;
43+
});
44+
45+
it('returns true if env var is to true (case-insensitive)', async () => {
46+
process.env.DASH0_TEST_IS_SET = 'tRuE';
47+
expect(hasOptedIn('DASH0_TEST_IS_SET')).to.be.true;
48+
});
49+
});
50+
51+
describe('opt-out', () => {
52+
it('returns false if env var is not set', async () => {
53+
expect(hasOptedOut('DASH0_TEST_IS_NOT_SET')).to.be.false;
54+
});
55+
56+
it('returns false if env var is not set to false', async () => {
57+
process.env.DASH0_TEST_IS_SET = 'whatever';
58+
expect(hasOptedOut('DASH0_TEST_IS_SET')).to.be.false;
59+
});
60+
61+
it('returns true if env var is to false', async () => {
62+
process.env.DASH0_TEST_IS_SET = 'false';
63+
expect(hasOptedOut('DASH0_TEST_IS_SET')).to.be.true;
64+
});
65+
66+
it('returns true if env var is to false (case-insensitive)', async () => {
67+
process.env.DASH0_TEST_IS_SET = 'fAlSe';
68+
expect(hasOptedOut('DASH0_TEST_IS_SET')).to.be.true;
69+
});
70+
});
71+
72+
describe('numeric', () => {
73+
it('returns default if env var is not set', async () => {
74+
expect(parseNumericEnvironmentVariableWithDefault('DASH0_TEST_IS_NOT_SET', 123)).to.equal(123);
75+
});
76+
77+
it('returns default if env var is set but cannot be parsed', async () => {
78+
process.env.DASH0_TEST_NOT_A_NUMBER = 'abc';
79+
expect(parseNumericEnvironmentVariableWithDefault('DASH0_TEST_NOT_A_NUMBER', 456)).to.equal(456);
80+
});
81+
82+
it('returns parsed value if env var is set and can be parsed', async () => {
83+
process.env.DASH0_TEST_NUMERIC_ENV_VAR = '1302';
84+
expect(parseNumericEnvironmentVariableWithDefault('DASH0_TEST_NUMERIC_ENV_VAR', 789)).to.equal(1302);
85+
});
86+
});
87+
});

0 commit comments

Comments
 (0)