Skip to content

Commit

Permalink
feat: minium version check
Browse files Browse the repository at this point in the history
  • Loading branch information
basti1302 committed May 14, 2024
1 parent c585090 commit 650b484
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 105 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,33 @@ jobs:
- name: test
run: |
npm test
verify-minimum-version-check:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [ '16.x' ]

name: Verify Minimum Version Check (Node.js ${{ matrix.node-version }})

steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: versions
run: |
node --version
npm --version
- run: npm ci

- name: integration test
run: |
npm run test:integration
26 changes: 4 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"nyc": "^15.1.0",
"prettier": "^3.2.5",
"protobufjs": "^7.2.6",
"semver": "^7.6.2",
"sinon": "^17.0.1",
"ts-node": "^10.9.2",
"ts-proto": "^1.172.0",
Expand Down
100 changes: 38 additions & 62 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2024 Dash0 Inc.
// SPDX-License-Identifier: Apache-2.0

import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { Detector, DetectorSync, envDetector, hostDetector, processDetector, Resource } from '@opentelemetry/resources';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { NodeSDK, NodeSDKConfiguration } from '@opentelemetry/sdk-node';

import { version } from '../package.json';
import PodUidDetector from './detectors/node/opentelemetry-resource-detector-kubernetes-pod';
import ServiceNameFallbackDetector from './detectors/node/opentelemetry-resource-detector-service-name-fallback';
import { getResourceDetectorsFromEnv } from './util/sdkUtil';

if (process.env.DASH0_DEBUG) {
console.log('Dash0 OpenTelemetry distribution for Node.js: Starting NodeSDK.');
}

let baseUrl = 'http://dash0-opentelemetry-collector-daemonset.default.svc.cluster.local:4318';
if (process.env.DASH0_OTEL_COLLECTOR_BASE_URL) {
baseUrl = process.env.DASH0_OTEL_COLLECTOR_BASE_URL;
}

const instrumentationConfig: any = {};
if (
!process.env.DASH0_ENABLE_FS_INSTRUMENTATION ||
process.env.DASH0_ENABLE_FS_INSTRUMENTATION.trim().toLowerCase() !== 'true'
) {
instrumentationConfig['@opentelemetry/instrumentation-fs'] = {
enabled: false,
};
const majorVersionLowerBound = 18;
const majorVersionTestedUpperBound = 22;
const prefix = 'Dash0 OpenTelemtry Distribution';

function init() {
try {
const nodeJsRuntimeVersion = process.version;
const match = nodeJsRuntimeVersion.match(/v(?<majorVersion>\d+)\.(?:\d+)\.(?:\d+)/);
if (!match || !match.groups) {
logProhibitiveError(`Cannot parse Node.js runtime version ${nodeJsRuntimeVersion}.`);
return;
}
const majorVersion = parseInt(match.groups.majorVersion, 10);
if (isNaN(majorVersion)) {
logProhibitiveError(`Cannot parse Node.js runtime version ${nodeJsRuntimeVersion}.`);
return;
}
if (majorVersion < majorVersionLowerBound) {
logProhibitiveError(
`The distribution does not support this Node.js runtime version (${nodeJsRuntimeVersion}). The minimum supported version is Node.js ${majorVersionLowerBound}.0.0.`,
);
return;
}
if (majorVersion > majorVersionTestedUpperBound) {
logWarning(
`Please note: The distribution has not been explicitly tested with this Node.js runtime version (${nodeJsRuntimeVersion}). The maximum tested version is Node.js ${majorVersionTestedUpperBound}.`,
);
}

require('./init');
} catch (e) {
logProhibitiveError(`Initialization failed: ${e}`);
}
}

const configuration: Partial<NodeSDKConfiguration> = {
traceExporter: new OTLPTraceExporter({
url: `${baseUrl}/v1/traces`,
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: `${baseUrl}/v1/metrics`,
}),
}),

instrumentations: [getNodeAutoInstrumentations(instrumentationConfig)],

resource: new Resource({
'telemetry.distro.name': 'dash0-nodejs',
'telemetry.distro.version': version,
}),
};
init();

// Copy the behavior of the NodeSDK constructor with regard to resource detectors, but add the pod uid detector.
// https://github.com/open-telemetry/opentelemetry-js/blob/73fddf9b5e7a93bd4cf21c2dbf444cee31d26c88/experimental/packages/opentelemetry-sdk-node/src/sdk.ts#L126-L132
let detectors: (Detector | DetectorSync)[];
if (process.env.OTEL_NODE_RESOURCE_DETECTORS != null) {
detectors = getResourceDetectorsFromEnv();
} else {
detectors = [envDetector, processDetector, hostDetector];
function logProhibitiveError(message: string) {
console.error(`[${prefix}] ${message} OpenTelemetry data will not be sent to Dash0.`);
}
detectors.push(new PodUidDetector());
detectors.push(new ServiceNameFallbackDetector());
configuration.resourceDetectors = detectors;

const sdk = new NodeSDK(configuration);

sdk.start();

if (process.env.DASH0_DEBUG) {
console.log('Dash0 OpenTelemetry distribution for Node.js: NodeSDK started.');
function logWarning(message: string) {
console.error(`[${prefix}] ${message}`);
}
71 changes: 71 additions & 0 deletions src/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright 2024 Dash0 Inc.
// SPDX-License-Identifier: Apache-2.0

import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { Detector, DetectorSync, envDetector, hostDetector, processDetector, Resource } from '@opentelemetry/resources';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { NodeSDK, NodeSDKConfiguration } from '@opentelemetry/sdk-node';

import { version } from '../package.json';
import PodUidDetector from './detectors/node/opentelemetry-resource-detector-kubernetes-pod';
import ServiceNameFallbackDetector from './detectors/node/opentelemetry-resource-detector-service-name-fallback';
import { getResourceDetectorsFromEnv } from './util/sdkUtil';

if (process.env.DASH0_DEBUG) {
console.log('Dash0 OpenTelemetry distribution for Node.js: Starting NodeSDK.');
}

let baseUrl = 'http://dash0-opentelemetry-collector-daemonset.default.svc.cluster.local:4318';
if (process.env.DASH0_OTEL_COLLECTOR_BASE_URL) {
baseUrl = process.env.DASH0_OTEL_COLLECTOR_BASE_URL;
}

const instrumentationConfig: any = {};
if (
!process.env.DASH0_ENABLE_FS_INSTRUMENTATION ||
process.env.DASH0_ENABLE_FS_INSTRUMENTATION.trim().toLowerCase() !== 'true'
) {
instrumentationConfig['@opentelemetry/instrumentation-fs'] = {
enabled: false,
};
}

const configuration: Partial<NodeSDKConfiguration> = {
traceExporter: new OTLPTraceExporter({
url: `${baseUrl}/v1/traces`,
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: `${baseUrl}/v1/metrics`,
}),
}),

instrumentations: [getNodeAutoInstrumentations(instrumentationConfig)],

resource: new Resource({
'telemetry.distro.name': 'dash0-nodejs',
'telemetry.distro.version': version,
}),
};

// Copy the behavior of the NodeSDK constructor with regard to resource detectors, but add the pod uid detector.
// https://github.com/open-telemetry/opentelemetry-js/blob/73fddf9b5e7a93bd4cf21c2dbf444cee31d26c88/experimental/packages/opentelemetry-sdk-node/src/sdk.ts#L126-L132
let detectors: (Detector | DetectorSync)[];
if (process.env.OTEL_NODE_RESOURCE_DETECTORS != null) {
detectors = getResourceDetectorsFromEnv();
} else {
detectors = [envDetector, processDetector, hostDetector];
}
detectors.push(new PodUidDetector());
detectors.push(new ServiceNameFallbackDetector());
configuration.resourceDetectors = detectors;

const sdk = new NodeSDK(configuration);

sdk.start();

if (process.env.DASH0_DEBUG) {
console.log('Dash0 OpenTelemetry distribution for Node.js: NodeSDK started.');
}
2 changes: 2 additions & 0 deletions test/.mocharc.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
module.exports = {
extension: ['ts'],
ignore: ['test/**/node_modules/**'],
// As long as we test the minimum version check with a Node.js version < 18.0.0, we need to enable fetch explicitly.
'node-option': ['experimental-fetch'],
recursive: true,
require: ['ts-node/register'],
slow: 3000,
Expand Down
5 changes: 5 additions & 0 deletions test/collector/CollectorChildProcessWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export default class CollectorChildProcessWrapper extends ChildProcessWrapper {
return stats.traces >= 1;
}

async hasTelemetry() {
const stats = await collector().fetchStats();
return stats.traces >= 1 || stats.metrics >= 1 || stats.logs >= 1;
}

async fetchTelemetry() {
return <OpenTelemetryData>await collector().sendRequest({ command: 'telemetry' });
}
Expand Down
16 changes: 16 additions & 0 deletions test/integration/ChildProcessWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,19 @@ export default class ChildProcessWrapper {
}

class ResponseEmitter extends EventEmitter {}

export function defaultAppConfiguration(appPort: number): ChildProcessWrapperOptions {
return {
path: 'test/apps/express-typescript',
label: 'app',
useTsNode: true,
useDistro: true,
env: {
PORT: appPort.toString(),
// have the Node.js SDK send spans every 100 ms instead of every 5 seconcds to speed up tests
OTEL_BSP_SCHEDULE_DELAY: '100',
DASH0_OTEL_COLLECTOR_BASE_URL: 'http://localhost:4318',
// OTEL_LOG_LEVEL: 'VERBOSE',
},
};
}
55 changes: 55 additions & 0 deletions test/integration/minimumVersion_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2024 Dash0 Inc.
// SPDX-License-Identifier: Apache-2.0

import { expect } from 'chai';
import semver from 'semver';
import delay from '../util/delay';

import runCommand from '../util/runCommand';
import ChildProcessWrapper, { defaultAppConfiguration } from './ChildProcessWrapper';
import { collector } from './rootHooks';

const { fail } = expect;

const appPort = 1302;

// This test verifies that the distribution does not attempt to initialize itself if the Node.js runtime version is too
// old.
const skipWhenNodeJsVersionIsGreaterOrEquals = '18.0.0';

describe('minimum version check', () => {
let appUnderTest: ChildProcessWrapper;

before(async function () {
if (semver.gte(process.version, skipWhenNodeJsVersionIsGreaterOrEquals)) {
// This suite is deliberatly skipped on supported versions and only run on a Node.js version that is below the
// minimum supported version.
this.skip();
return;
}

await runCommand('npm ci', 'test/apps/express-typescript');
const appConfiguration = defaultAppConfiguration(appPort);
appUnderTest = new ChildProcessWrapper(appConfiguration);
await appUnderTest.start();
});

after(async () => {
if (appUnderTest) {
await appUnderTest.stop();
}
});

it('should stand down without loading the distribution', async () => {
await delay(1000);
const response = await fetch(`http://localhost:${appPort}/ohai`);
await delay(2000);
expect(response.status).to.equal(200);
const responsePayload = await response.json();
expect(responsePayload).to.deep.equal({ message: 'We make Observability easy for every developer.' });

if (await collector().hasTelemetry()) {
fail('The collector received telemetry data although it should not have received anything.');
}
});
});
Loading

0 comments on commit 650b484

Please sign in to comment.