diff --git a/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts b/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts index d03afadb93..f631f001ee 100644 --- a/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts +++ b/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts @@ -59,6 +59,18 @@ export abstract class InstrumentationBase } } + private _extractPackageVersion(baseDir: string): string | undefined { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const version = require(path.join(baseDir, 'package.json')).version; + return typeof version === 'string' ? version : undefined; + } catch (error) { + diag.warn('Failed extracting version', baseDir); + } + + return undefined; + } + private _onRequire( module: InstrumentationModuleDefinition, exports: T, @@ -73,13 +85,11 @@ export abstract class InstrumentationBase return exports; } - // eslint-disable-next-line @typescript-eslint/no-var-requires - const version = require(path.join(baseDir, 'package.json')).version; + const version = this._extractPackageVersion(baseDir); module.moduleVersion = version; if (module.name === name) { // main module if ( - typeof version === 'string' && isSupported(module.supportedVersions, version, module.includePrerelease) ) { if (typeof module.patch === 'function') { @@ -167,7 +177,12 @@ export abstract class InstrumentationBase } } -function isSupported(supportedVersions: string[], version: string, includePrerelease?: boolean): boolean { +function isSupported(supportedVersions: string[], version?: string, includePrerelease?: boolean): boolean { + if (typeof version === 'undefined') { + // If we don't have the version, accept the wildcard case only + return supportedVersions.includes('*'); + } + return supportedVersions.some(supportedVersion => { return satisfies(version, supportedVersion, { includePrerelease }); }); diff --git a/packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts b/packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts new file mode 100644 index 0000000000..e1c42681d3 --- /dev/null +++ b/packages/opentelemetry-instrumentation/test/node/InstrumentationBase.test.ts @@ -0,0 +1,166 @@ +/* + * 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 * as assert from 'assert'; +import * as sinon from 'sinon'; +import { InstrumentationBase, InstrumentationModuleDefinition } from '../../src'; + +const MODULE_NAME = 'test-module'; +const MODULE_FILE_NAME = 'test-module-file'; +const MODULE_VERSION = '0.1.0'; +const WILDCARD_VERSION = '*'; +const MODULE_DIR = '/random/dir'; + +class TestInstrumentation extends InstrumentationBase { + constructor() { + super(MODULE_NAME, MODULE_VERSION); + } + + init() {} +} + +describe('InstrumentationBase', () => { + describe('_onRequire - module version is not available', () => { + // For all of these cases, there is no indication of the actual module version, + // so we require there to be a wildcard supported version. + + let instrumentation: TestInstrumentation; + let modulePatchSpy: sinon.SinonSpy; + + beforeEach(() => { + instrumentation = new TestInstrumentation(); + // @ts-expect-error access internal property for testing + instrumentation._enabled = true; + modulePatchSpy = sinon.spy(); + }); + + describe('when patching a module', () => { + describe('AND there is no wildcard supported version', () => { + it('should not patch module', () => { + const moduleExports = {}; + const instrumentationModule = { + supportedVersions: [`^${MODULE_VERSION}`], + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.moduleExports, undefined); + sinon.assert.notCalled(modulePatchSpy); + }); + }); + + describe('AND there is a wildcard supported version', () => { + it('should patch module', () => { + const moduleExports = {}; + const instrumentationModule = { + supportedVersions: [`^${MODULE_VERSION}`, WILDCARD_VERSION], + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.moduleExports, moduleExports); + sinon.assert.calledOnceWithExactly(modulePatchSpy, moduleExports, undefined); + }); + }); + }); + + describe('when patching module files', () => { + let filePatchSpy: sinon.SinonSpy; + + beforeEach(() => { + filePatchSpy = sinon.spy(); + }) + + describe('AND there is no wildcard supported version', () => { + it('should not patch module file', () => { + const moduleExports = {}; + const supportedVersions = [`^${MODULE_VERSION}`]; + const instrumentationModule = { + supportedVersions, + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + files: [{ + name: MODULE_FILE_NAME, + supportedVersions, + patch: filePatchSpy as unknown + }] + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_FILE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.moduleExports, undefined); + sinon.assert.notCalled(modulePatchSpy); + sinon.assert.notCalled(filePatchSpy); + }); + }); + + describe('AND there is a wildcard supported version', () => { + it('should patch module file', () => { + const moduleExports = {}; + const supportedVersions = [`^${MODULE_VERSION}`, WILDCARD_VERSION]; + const instrumentationModule = { + supportedVersions, + name: MODULE_NAME, + patch: modulePatchSpy as unknown, + files: [{ + name: MODULE_FILE_NAME, + supportedVersions, + patch: filePatchSpy as unknown + }] + } as InstrumentationModuleDefinition; + + // @ts-expect-error access internal property for testing + instrumentation._onRequire( + instrumentationModule, + moduleExports, + MODULE_FILE_NAME, + MODULE_DIR + ); + + assert.strictEqual(instrumentationModule.moduleVersion, undefined); + assert.strictEqual(instrumentationModule.files[0].moduleExports, moduleExports); + sinon.assert.notCalled(modulePatchSpy); + sinon.assert.calledOnceWithExactly(filePatchSpy, moduleExports, undefined); + }); + }); + }); + }); +});