From 3a613a7d3ab1d083fb5e352ba197da49ed523f7b Mon Sep 17 00:00:00 2001 From: Fabio Silva <37403306+fabiogomessilva@users.noreply.github.com> Date: Tue, 19 Jun 2018 15:13:40 -0300 Subject: [PATCH] feat: add mechanism to load internal module files to patch (#52) * feat: add mechanism to load internal module files to patch * refactor: change Plugin Interface and BasePlugin to implement GoF Template Method * refactor: change http plugin to new interface * refactor: change https plugin to new interface * refactor: change http2 plugin to new interface * refactor: change mongodb plugin to new interface * refactor: improve load internal file test case * refactor: rename methods of Plugin interface --- .../src/trace/instrumentation/base-plugin.ts | 127 ++++++++++++++++-- .../src/trace/instrumentation/types.ts | 23 +++- .../src/http.ts | 26 ++-- .../test/test-http.ts | 4 +- .../src/http2.ts | 20 +-- .../test/test-http2.ts | 2 +- .../src/https.ts | 24 ++-- .../test/test-https.ts | 4 +- .../src/mongodb.ts | 23 ++-- .../test/test-mongodb.ts | 2 +- .../trace/instrumentation/plugin-loader.ts | 10 +- .../index.ts | 17 +++ .../load-internal-file-module.ts | 59 ++++++++ .../package.json | 4 + .../simple-module.ts | 15 ++- .../enduser-simple-module-plugin.ts | 15 +-- .../load-internal-file-module/index.ts | 9 ++ .../load-internal-file-module/package.json | 4 + .../src/extra-module.ts | 4 + .../node_modules/simple-module/index.ts | 2 +- .../test/test-plugin-loader.ts | 47 +++++-- 21 files changed, 325 insertions(+), 116 deletions(-) create mode 100644 packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/index.ts create mode 100644 packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/load-internal-file-module.ts create mode 100644 packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/package.json create mode 100644 packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/index.ts create mode 100644 packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/package.json create mode 100644 packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/src/extra-module.ts diff --git a/packages/opencensus-core/src/trace/instrumentation/base-plugin.ts b/packages/opencensus-core/src/trace/instrumentation/base-plugin.ts index 51adad593..f8b101fb0 100644 --- a/packages/opencensus-core/src/trace/instrumentation/base-plugin.ts +++ b/packages/opencensus-core/src/trace/instrumentation/base-plugin.ts @@ -13,11 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as shimmer from 'shimmer'; +import * as path from 'path'; +import * as semver from 'semver'; + +import {logger} from '../../common/console-logger'; +import {Logger} from '../../common/types'; import * as modelTypes from '../model/types'; + import * as types from './types'; -// TODO: improve Jsdoc comments +/** + * Maps a name (key) representing a internal file module and its exports + */ +export type ModuleExportsMapping = { + // tslint:disable:no-any + [key: string]: any; +}; + /** This class represent the base to patch plugin. */ export abstract class BasePlugin implements types.Plugin { @@ -30,6 +42,14 @@ export abstract class BasePlugin implements types.Plugin { protected tracer: modelTypes.Tracer; /** The module version. */ protected version: string; + /** a logger */ + protected logger: Logger; + /** list of internal files that need patch and are not exported by default */ + protected readonly internalFileList: types.PluginInternalFiles; + /** internal files loaded */ + protected internalFilesExports: ModuleExportsMapping; + /** module directory - used to load internal files */ + protected basedir: string; /** * Constructs a new BasePlugin instance. @@ -44,26 +64,109 @@ export abstract class BasePlugin implements types.Plugin { * @param moduleExports nodejs module exports to set as context * @param tracer tracer relating to context * @param version module version description + * @param basedir module absolute path */ // tslint:disable:no-any - protected setPluginContext( - moduleExports: any, tracer: modelTypes.Tracer, version: string) { + private setPluginContext( + moduleExports: any, tracer: modelTypes.Tracer, version: string, + basedir?: string) { this.moduleExports = moduleExports; this.tracer = tracer; this.version = version; + this.basedir = basedir; + this.logger = tracer.logger; + this.internalFilesExports = this.loadInternalFiles(); } - // TODO: review this implementation - // From the perspective of an instrumentation module author, - // that applyUnpatch is abstract makes it seem like patching is optional, - // while unpatching is not. It should be the other way around + /** + * Method that enables the instrumentation patch. + * + * This method implements the GoF Template Method Pattern + * 'enable' is the invariant part of the pattern and + * 'applyPatch' the variant. + * + * @param moduleExports nodejs module exports from the module to patch + * @param tracer a tracer instance + * @param version version of the current instaled module to patch + * @param basedir module absolute path + */ + enable( + // tslint:disable:no-any + moduleExports: any, tracer: modelTypes.Tracer, version: string, + basedir: string) { + this.setPluginContext(moduleExports, tracer, version, basedir); + return this.applyPatch(); + } + + /** Method to disable the instrumentation */ + disable() { + this.applyUnpatch(); + } + /** + * This method implements the GoF Template Method Pattern, + * 'applyPatch' is the variant part, each instrumentation should + * implement its own version, 'enable' method is the invariant. + * Wil be called when enable is called. + * + */ // tslint:disable:no-any - applyPatch(moduleExports: any, tracer: modelTypes.Tracer, version: string): - any { - this.setPluginContext(moduleExports, tracer, version); + protected abstract applyPatch(): any; + protected abstract applyUnpatch(): void; + + + /** + * Load internal files according to version range + */ + private loadInternalFiles(): ModuleExportsMapping { + let result: ModuleExportsMapping = null; + if (this.internalFileList) { + this.logger.debug('loadInternalFiles %o', this.internalFileList); + Object.keys(this.internalFileList).forEach(versionRange => { + if (semver.satisfies(this.version, versionRange)) { + if (result) { + this.logger.warn( + 'Plugin for %s@%s, has overlap version range (%s) for internal files: %o', + this.moduleName, this.version, versionRange, + this.internalFileList); + } + result = this.loadInternalModuleFiles( + this.internalFileList[versionRange], this.basedir); + } + }); + if (!result) { + this.logger.debug( + 'No internal file could be loaded for %s@%s', this.moduleName, + this.version); + } + } + + return result; } - abstract applyUnpatch(): void; + + /** + * Load internal files from a module and set internalFilesExports + */ + private loadInternalModuleFiles( + extraModulesList: types.PluginNames, + basedir: string): ModuleExportsMapping { + const extraModules: ModuleExportsMapping = {}; + if (extraModulesList) { + Object.keys(extraModulesList).forEach(moduleName => { + try { + this.logger.debug('loading File %s', extraModulesList[moduleName]); + extraModules[moduleName] = + require(path.join(basedir, extraModulesList[moduleName])); + } catch (e) { + this.logger.error( + 'Could not load internal file %s of module %s. Error: %s', + path.join(basedir, extraModulesList[moduleName]), this.moduleName, + e.message); + } + }); + } + return extraModules; + } } diff --git a/packages/opencensus-core/src/trace/instrumentation/types.ts b/packages/opencensus-core/src/trace/instrumentation/types.ts index 5166623be..33c964d86 100644 --- a/packages/opencensus-core/src/trace/instrumentation/types.ts +++ b/packages/opencensus-core/src/trace/instrumentation/types.ts @@ -19,15 +19,20 @@ import {Tracer} from '../model/types'; /** Interface Plugin to apply patch. */ export interface Plugin { /** - * Method to apply the instrumentation patch + * Method that enables the instrumentation patch. + * * @param moduleExports nodejs module exports from the module to patch * @param tracer a tracer instance * @param version version of the current instaled module to patch + * @param basedir module absolute path */ - // tslint:disable:no-any - applyPatch(moduleExports: any, tracer: Tracer, version: string): any; - /** Method to unpatch the instrumentation */ - applyUnpatch(): void; + enable( + // tslint:disable-next-line:no-any + moduleExports: any, tracer: Tracer, version: string, + // tslint:disable-next-line:no-any + basedir?: string): any; + /** Method to disable the instrumentation */ + disable(): void; } @@ -39,3 +44,11 @@ export interface Plugin { export type PluginNames = { [pluginName: string]: string; }; + +/** + * Each key should be the name of the module to trace, and its value + * a mapping of a property name to a internal plugin file name. + */ +export type PluginInternalFiles = { + [versions: string]: PluginNames; +}; diff --git a/packages/opencensus-instrumentation-http/src/http.ts b/packages/opencensus-instrumentation-http/src/http.ts index 125e384a6..6ef038da7 100644 --- a/packages/opencensus-instrumentation-http/src/http.ts +++ b/packages/opencensus-instrumentation-http/src/http.ts @@ -44,7 +44,6 @@ export class HttpPlugin extends classes.BasePlugin { static ATTRIBUTE_HTTP_ERROR_NAME = 'http.error_name'; static ATTRIBUTE_HTTP_ERROR_MESSAGE = 'http.error_message'; - logger: types.Logger; /** Constructs a new HttpPlugin instance. */ constructor(moduleName: string) { @@ -54,31 +53,24 @@ export class HttpPlugin extends classes.BasePlugin { /** * Patches HTTP incoming and outcoming request functions. - * @param moduleExports The http module exports - * @param tracer A tracer instance to create spans on. - * @param version The package version. */ - // tslint:disable-next-line:no-any - applyPatch(moduleExports: HttpModule, tracer: types.Tracer, version: string) { - this.setPluginContext(moduleExports, tracer, version); - this.logger = tracer.logger || logger.logger('debug'); - + protected applyPatch() { this.logger.debug('applying pacth to %s@%s', this.moduleName, this.version); shimmer.wrap( - moduleExports, 'request', this.getPatchOutgoingRequestFunction()); + this.moduleExports, 'request', this.getPatchOutgoingRequestFunction()); // In Node 8, http.get calls a private request method, therefore we patch it // here too. - if (semver.satisfies(version, '>=8.0.0')) { + if (semver.satisfies(this.version, '>=8.0.0')) { shimmer.wrap( - moduleExports, 'get', this.getPatchOutgoingRequestFunction()); + this.moduleExports, 'get', this.getPatchOutgoingRequestFunction()); } - if (moduleExports && moduleExports.Server && - moduleExports.Server.prototype) { + if (this.moduleExports && this.moduleExports.Server && + this.moduleExports.Server.prototype) { shimmer.wrap( - moduleExports.Server.prototype, 'emit', + this.moduleExports.Server.prototype, 'emit', this.getPatchIncomingRequestFunction()); } else { this.logger.error( @@ -86,12 +78,12 @@ export class HttpPlugin extends classes.BasePlugin { this.moduleName); } - return moduleExports; + return this.moduleExports; } /** Unpatches all HTTP patched function. */ - applyUnpatch(): void { + protected applyUnpatch(): void { shimmer.unwrap(this.moduleExports, 'request'); if (semver.satisfies(this.version, '>=8.0.0')) { shimmer.unwrap(this.moduleExports, 'get'); diff --git a/packages/opencensus-instrumentation-http/test/test-http.ts b/packages/opencensus-instrumentation-http/test/test-http.ts index 29331da7f..7604b5396 100644 --- a/packages/opencensus-instrumentation-http/test/test-http.ts +++ b/packages/opencensus-instrumentation-http/test/test-http.ts @@ -100,7 +100,7 @@ describe('HttpPlugin', () => { }); before(() => { - plugin.applyPatch(http, tracer, VERSION); + plugin.enable(http, tracer, VERSION, null); tracer.registerSpanEventListener(rootSpanVerifier); server = http.createServer((request, response) => { response.end('Test Server Response'); @@ -283,7 +283,7 @@ describe('HttpPlugin', () => { /** Should not intercept incoming and outgoing requests */ describe('applyUnpatch()', () => { it('should not create a root span for incoming requests', async () => { - plugin.applyUnpatch(); + plugin.disable(); const testPath = '/incoming/unpatch/'; nock.enableNetConnect(); diff --git a/packages/opencensus-instrumentation-http2/src/http2.ts b/packages/opencensus-instrumentation-http2/src/http2.ts index a05d44135..67d83be0d 100644 --- a/packages/opencensus-instrumentation-http2/src/http2.ts +++ b/packages/opencensus-instrumentation-http2/src/http2.ts @@ -42,28 +42,22 @@ export class Http2Plugin extends HttpPlugin { /** * Patches HTTP2 incoming and outcoming request functions. - * @param moduleExporters The HTTP2 package. - * @param tracer A tracer instance to create spans on. - * @param version The package version. */ - // tslint:disable-next-line:no-any - applyPatch(moduleExports: any, tracer: types.Tracer, version: string) { - this.setPluginContext(moduleExports, tracer, version); - this.logger = tracer.logger || logger.logger('debug'); - + protected applyPatch() { shimmer.wrap( - moduleExports, 'createServer', this.getPatchCreateServerFunction()); + this.moduleExports, 'createServer', + this.getPatchCreateServerFunction()); shimmer.wrap( - moduleExports, 'createSecureServer', + this.moduleExports, 'createSecureServer', this.getPatchCreateServerFunction()); - shimmer.wrap(moduleExports, 'connect', this.getPatchConnectFunction()); + shimmer.wrap(this.moduleExports, 'connect', this.getPatchConnectFunction()); - return moduleExports; + return this.moduleExports; } /** Unpatches all HTTP2 patched function. */ - applyUnpatch(): void { + protected applyUnpatch(): void { // Only Client and Server constructors will be unwrapped. Any existing // Client or Server instances will still trace shimmer.unwrap(this.moduleExports, 'createServer'); diff --git a/packages/opencensus-instrumentation-http2/test/test-http2.ts b/packages/opencensus-instrumentation-http2/test/test-http2.ts index 6a42abdcc..63606e688 100644 --- a/packages/opencensus-instrumentation-http2/test/test-http2.ts +++ b/packages/opencensus-instrumentation-http2/test/test-http2.ts @@ -100,7 +100,7 @@ describe('Http2Plugin', () => { before(() => { tracer.registerSpanEventListener(rootSpanVerifier); - plugin.applyPatch(http2, tracer, VERSION); + plugin.enable(http2, tracer, VERSION, null); server = http2.createServer(); server.on('stream', (stream, requestHeaders) => { const statusCode = requestHeaders[':path'].length > 1 ? diff --git a/packages/opencensus-instrumentation-https/src/https.ts b/packages/opencensus-instrumentation-https/src/https.ts index 7e89d12b6..f130dfd2d 100644 --- a/packages/opencensus-instrumentation-https/src/https.ts +++ b/packages/opencensus-instrumentation-https/src/https.ts @@ -30,22 +30,15 @@ export class HttpsPlugin extends HttpPlugin { /** * Patches HTTPS incoming and outcoming request functions. - * @param moduleExports The HTTPS package. - * @param tracer A tracer instance to create spans on. - * @param version The package version. */ - // tslint:disable:no-any - applyPatch(moduleExports: any, tracer: types.Tracer, version: string) { - this.setPluginContext(moduleExports, tracer, version); - this.logger = tracer.logger || logger.logger('debug'); - + protected applyPatch() { this.logger.debug('applying pacth to %s@%s', this.moduleName, this.version); - if (moduleExports && moduleExports.Server && - moduleExports.Server.prototype) { + if (this.moduleExports && this.moduleExports.Server && + this.moduleExports.Server.prototype) { shimmer.wrap( - moduleExports && moduleExports.Server && - moduleExports.Server.prototype, + this.moduleExports && this.moduleExports.Server && + this.moduleExports.Server.prototype, 'emit', this.getPatchIncomingRequestFunction()); } else { this.logger.error( @@ -55,13 +48,14 @@ export class HttpsPlugin extends HttpPlugin { // TODO: review the need to patch 'request' - shimmer.wrap(moduleExports, 'get', this.getPatchOutgoingRequestFunction()); + shimmer.wrap( + this.moduleExports, 'get', this.getPatchOutgoingRequestFunction()); - return moduleExports; + return this.moduleExports; } /** Unpatches all HTTPS patched function. */ - applyUnpatch(): void { + protected applyUnpatch(): void { if (this.moduleExports && this.moduleExports.Server && this.moduleExports.Server.prototype) { shimmer.unwrap( diff --git a/packages/opencensus-instrumentation-https/test/test-https.ts b/packages/opencensus-instrumentation-https/test/test-https.ts index 121e16d17..f4c8e1345 100644 --- a/packages/opencensus-instrumentation-https/test/test-https.ts +++ b/packages/opencensus-instrumentation-https/test/test-https.ts @@ -108,7 +108,7 @@ describe('HttpsPlugin', () => { }); before(() => { - plugin.applyPatch(https, tracer, VERSION); + plugin.enable(https, tracer, VERSION, null); tracer.registerSpanEventListener(rootSpanVerifier); server = https.createServer(httpsOptions, (request, response) => { response.end('Test Server Response'); @@ -289,7 +289,7 @@ describe('HttpsPlugin', () => { /** Should not intercept incoming and outgoing requests */ describe('applyUnpatch()', () => { it('should not create a root span for incoming requests', async () => { - plugin.applyUnpatch(); + plugin.disable(); const testPath = '/incoming/unpatch/'; const options = {host: 'localhost', path: testPath, port: serverPort}; diff --git a/packages/opencensus-instrumentation-mongodb/src/mongodb.ts b/packages/opencensus-instrumentation-mongodb/src/mongodb.ts index f9dcc54ef..6bbd9cb05 100644 --- a/packages/opencensus-instrumentation-mongodb/src/mongodb.ts +++ b/packages/opencensus-instrumentation-mongodb/src/mongodb.ts @@ -28,7 +28,6 @@ export class MongoDBPlugin extends classes.BasePlugin { private readonly SERVER_FNS = ['insert', 'update', 'remove', 'auth']; private readonly CURSOR_FNS_FIRST = ['_find', '_getmore']; private readonly SPAN_MONGODB_QUERY_TYPE = 'db.mongodb.query'; - private logger: types.Logger; /** Constructs a new MongoDBPlugin instance. */ constructor(moduleName: string) { @@ -37,38 +36,32 @@ export class MongoDBPlugin extends classes.BasePlugin { /** * Patches MongoDB operations. - * @param moduleExports The mongodb module exports. - * @param tracer A tracer instance to create spans on. - * @param version The package version. */ - applyPatch(moduleExports: MongoDB, tracer: types.Tracer, version: string) { - this.setPluginContext(moduleExports, tracer, version); - this.logger = tracer.logger || logger.logger('debug'); - + protected applyPatch() { this.logger.debug('Patched MongoDB'); - if (moduleExports.Server) { + if (this.moduleExports.Server) { this.logger.debug('patching mongodb-core.Server.prototype.command'); shimmer.wrap( - moduleExports.Server.prototype, 'command' as never, + this.moduleExports.Server.prototype, 'command' as never, this.getPatchCommand()); this.logger.debug( 'patching mongodb-core.Server.prototype functions:', this.SERVER_FNS); shimmer.massWrap( - [moduleExports.Server.prototype], this.SERVER_FNS as never[], + [this.moduleExports.Server.prototype], this.SERVER_FNS as never[], this.getPatchQuery()); } - if (moduleExports.Cursor) { + if (this.moduleExports.Cursor) { this.logger.debug( 'patching mongodb-core.Cursor.prototype functions:', this.CURSOR_FNS_FIRST); shimmer.massWrap( - [moduleExports.Cursor.prototype], this.CURSOR_FNS_FIRST as never[], - this.getPatchCursor()); + [this.moduleExports.Cursor.prototype], + this.CURSOR_FNS_FIRST as never[], this.getPatchCursor()); } - return moduleExports; + return this.moduleExports; } /** Unpatches all MongoDB patched functions. */ diff --git a/packages/opencensus-instrumentation-mongodb/test/test-mongodb.ts b/packages/opencensus-instrumentation-mongodb/test/test-mongodb.ts index 03ab86f16..db7820e90 100644 --- a/packages/opencensus-instrumentation-mongodb/test/test-mongodb.ts +++ b/packages/opencensus-instrumentation-mongodb/test/test-mongodb.ts @@ -105,7 +105,7 @@ describe('MongoDBPlugin', () => { before((done) => { tracer.start({samplingRate: 1}); tracer.registerSpanEventListener(rootSpanVerifier); - plugin.applyPatch(mongodb, tracer, VERSION); + plugin.enable(mongodb, tracer, VERSION, null); accessCollection(URL, DB_NAME, COLLECTION_NAME) .then(result => { client = result.client; diff --git a/packages/opencensus-nodejs/src/trace/instrumentation/plugin-loader.ts b/packages/opencensus-nodejs/src/trace/instrumentation/plugin-loader.ts index 8494e8940..eeeec9364 100644 --- a/packages/opencensus-nodejs/src/trace/instrumentation/plugin-loader.ts +++ b/packages/opencensus-nodejs/src/trace/instrumentation/plugin-loader.ts @@ -107,9 +107,9 @@ export class PluginLoader { hook(Object.keys(pluginList), (exports, name, basedir) => { const version = this.getPackageVersion(name, basedir as string); this.logger.info('trying loading %s.%s', name, version); - let result = exports; + let moduleExports = exports; if (!version) { - return result; + return moduleExports; } else { this.logger.debug('applying patch to %s@%s module', name, version); this.logger.debug( @@ -118,13 +118,13 @@ export class PluginLoader { try { const plugin: types.Plugin = require(pluginList[name]).plugin; this.plugins.push(plugin); - result = plugin.applyPatch(exports, this.tracer, version); + moduleExports = plugin.enable(exports, this.tracer, version, basedir); } catch (e) { this.logger.error( 'could not load plugin %s of module %s. Error: %s', pluginList[name], name, e.message); } - return result; + return moduleExports; } }); } @@ -133,7 +133,7 @@ export class PluginLoader { /** Unloads plugins. */ unloadPlugins() { for (const plugin of this.plugins) { - plugin.applyUnpatch(); + plugin.disable(); } this.plugins = []; } diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/index.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/index.ts new file mode 100644 index 000000000..0ab4571ae --- /dev/null +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2018, OpenCensus 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 + * + * http://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. + */ + +export * from './load-internal-file-module'; diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/load-internal-file-module.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/load-internal-file-module.ts new file mode 100644 index 000000000..ec5ff7f26 --- /dev/null +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/load-internal-file-module.ts @@ -0,0 +1,59 @@ +/** + * Copyright 2018, OpenCensus 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 + * + * http://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 {types} from '@opencensus/opencensus-core'; +import {classes} from '@opencensus/opencensus-core'; +import * as shimmer from 'shimmer'; + + +export class SimpleModulePlugin extends classes.BasePlugin { + protected internalFileList: types.PluginInternalFiles = { + '~0.0.1': {'extra-module': 'src/extra-module'} + }; + + constructor() { + super('load-internal-file-module'); + } + + + protected applyPatch() { + shimmer.wrap( + this.moduleExports, 'name', (orig) => () => 'patched-' + orig.apply()); + + shimmer.wrap(this.moduleExports, 'value', (orig) => () => orig.apply() + 1); + + + if (this.internalFilesExports) { + shimmer.wrap( + this.internalFilesExports['extra-module'], 'name', + (orig) => () => 'patched-' + orig.apply()); + + shimmer.wrap( + this.internalFilesExports['extra-module'], 'value', + (orig) => () => orig.apply() + 1); + } + + return this.moduleExports; + } + + protected applyUnpatch(): void { + shimmer.unwrap(this.moduleExports, 'name'); + shimmer.unwrap(this.moduleExports, 'value'); + } +} + +const plugin = new SimpleModulePlugin(); +export {plugin}; diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/package.json b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/package.json new file mode 100644 index 000000000..2969f641c --- /dev/null +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-load-internal-file-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "@opencensus/opencensus-instrumentation-load-internal-file-module", + "version": "0.0.1" +} \ No newline at end of file diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-simple-module/simple-module.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-simple-module/simple-module.ts index 8c4c9be7d..41ef66214 100644 --- a/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-simple-module/simple-module.ts +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/@opencensus/instrumentation-simple-module/simple-module.ts @@ -24,18 +24,19 @@ export class SimpleModulePlugin extends classes.BasePlugin { super('simple-module'); } - // tslint:disable:no-any - applyPatch(moduleExports: any, tracer: types.Tracer, version: string) { - this.setPluginContext(moduleExports, tracer, version); + + protected applyPatch() { + this.logger.debug('apply path to simple module'); shimmer.wrap( - moduleExports, 'name', (orig) => () => 'patched-' + orig.apply()); + this.moduleExports, 'name', (orig) => () => 'patched-' + orig.apply()); + + shimmer.wrap(this.moduleExports, 'value', (orig) => () => orig.apply() + 1); - shimmer.wrap(moduleExports, 'value', (orig) => () => orig.apply() + 1); - return moduleExports; + return this.moduleExports; } - applyUnpatch(): void { + protected applyUnpatch(): void { shimmer.unwrap(this.moduleExports, 'name'); shimmer.unwrap(this.moduleExports, 'value'); } diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/enduser-simple-module-plugin/enduser-simple-module-plugin.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/enduser-simple-module-plugin/enduser-simple-module-plugin.ts index fce5adc6c..f21b898c3 100644 --- a/packages/opencensus-nodejs/test/instrumentation/node_modules/enduser-simple-module-plugin/enduser-simple-module-plugin.ts +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/enduser-simple-module-plugin/enduser-simple-module-plugin.ts @@ -24,18 +24,15 @@ export class MySimpleModulePlugin extends classes.BasePlugin { super('simple-module'); } - // tslint:disable:no-any - applyPatch(moduleExports: any, tracer: types.Tracer, version: string) { - this.setPluginContext(moduleExports, tracer, version); - // tslint:disable:no-any + protected applyPatch() { shimmer.wrap( - moduleExports, 'name', (orig) => () => 'my-patched-' + orig.apply()); - // tslint:disable:no-any - shimmer.wrap(moduleExports, 'value', (orig) => () => orig.apply() + 2); - return moduleExports; + this.moduleExports, 'name', + (orig) => () => 'my-patched-' + orig.apply()); + shimmer.wrap(this.moduleExports, 'value', (orig) => () => orig.apply() + 2); + return this.moduleExports; } - applyUnpatch(): void { + protected applyUnpatch(): void { shimmer.unwrap(this.moduleExports, 'name'); shimmer.unwrap(this.moduleExports, 'value'); } diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/index.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/index.ts new file mode 100644 index 000000000..35edb1f74 --- /dev/null +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/index.ts @@ -0,0 +1,9 @@ + +const extraModule = require('./src/extra-module'); + +export = { + name: () => 'load-internal-file-module', + value: () => 110, + extraName: () => extraModule.name(), + extraValue: () => extraModule.value() +}; diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/package.json b/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/package.json new file mode 100644 index 000000000..2d7c261cd --- /dev/null +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "load-internal-file-module", + "version": "0.0.1" +} \ No newline at end of file diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/src/extra-module.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/src/extra-module.ts new file mode 100644 index 000000000..5c4336e55 --- /dev/null +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/load-internal-file-module/src/extra-module.ts @@ -0,0 +1,4 @@ +export = { + name: () => 'extra-module', + value: () => 120 +}; diff --git a/packages/opencensus-nodejs/test/instrumentation/node_modules/simple-module/index.ts b/packages/opencensus-nodejs/test/instrumentation/node_modules/simple-module/index.ts index 1825ef993..8c4803f1d 100644 --- a/packages/opencensus-nodejs/test/instrumentation/node_modules/simple-module/index.ts +++ b/packages/opencensus-nodejs/test/instrumentation/node_modules/simple-module/index.ts @@ -1,4 +1,4 @@ -module.exports = { +export = { name: () => 'simple-module', value: () => 100 }; diff --git a/packages/opencensus-nodejs/test/test-plugin-loader.ts b/packages/opencensus-nodejs/test/test-plugin-loader.ts index bbf143954..c3fe36bfe 100644 --- a/packages/opencensus-nodejs/test/test-plugin-loader.ts +++ b/packages/opencensus-nodejs/test/test-plugin-loader.ts @@ -16,31 +16,31 @@ import {classes, types} from '@opencensus/opencensus-core'; import {logger} from '@opencensus/opencensus-core'; - import * as assert from 'assert'; -import {isArray} from 'util'; +import * as path from 'path'; import {Constants} from '../src/trace/constants'; import {PluginLoader} from '../src/trace/instrumentation/plugin-loader'; -import {Tracing} from '../src/trace/tracing'; - -// TODO: Clarify requirement regarding instrumentation of nodejs core modules -// for Opencensus const INSTALLED_PLUGINS_PATH = `${__dirname}/instrumentation/node_modules`; const TEST_MODULES = [ - 'simple-module', // this module exist and has a plugin - 'nonexistent-module', // this module does not exist - 'http' // this module does not have a plugin + 'simple-module' // this module exist and has a plugin + , + 'nonexistent-module' // this module does not exist + , + 'http' // this module does not have a plugin + , + 'load-internal-file-module' // this module has an internal file not exported ]; + const clearRequireCache = () => { Object.keys(require.cache).forEach(key => delete require.cache[key]); }; describe('Plugin Loader', () => { - const log = logger.logger('error'); + const log = logger.logger(4); before(() => { module.paths.push(INSTALLED_PLUGINS_PATH); @@ -55,6 +55,7 @@ describe('Plugin Loader', () => { describe('PluginLoader', () => { const plugins = PluginLoader.defaultPluginsFromArray(TEST_MODULES); const tracer = new classes.Tracer(); + tracer.start({logger: log}); /** Should get the plugins to use. */ describe('static defaultPluginsFromArray()', () => { @@ -63,6 +64,7 @@ describe('Plugin Loader', () => { assert.ok(plugins[TEST_MODULES[0]]); assert.ok(plugins[TEST_MODULES[1]]); assert.ok(plugins[TEST_MODULES[2]]); + assert.ok(plugins[TEST_MODULES[3]]); assert.strictEqual( plugins[TEST_MODULES[0]], `@opencensus/${ @@ -75,6 +77,11 @@ describe('Plugin Loader', () => { assert.strictEqual( plugins[TEST_MODULES[2]], `@opencensus/${Constants.DEFAULT_PLUGIN_PACKAGE_NAME_PREFIX}-http`); + assert.strictEqual( + plugins[TEST_MODULES[3]], + `@opencensus/${ + Constants + .DEFAULT_PLUGIN_PACKAGE_NAME_PREFIX}-load-internal-file-module`); }); }); @@ -98,6 +105,24 @@ describe('Plugin Loader', () => { assert.strictEqual(simpleModule.value(), 101); }); + it('should load and patch extra plugin file', () => { + const pluginLoader = new PluginLoader(log, tracer); + assert.strictEqual(pluginLoader.plugins.length, 0); + pluginLoader.loadPlugins(plugins); + const moduleName = TEST_MODULES[3]; + const loadInternalFileModule = require(moduleName); + assert.strictEqual(pluginLoader.plugins.length, 1); + assert.strictEqual( + loadInternalFileModule.name(), 'patched-' + moduleName); + assert.strictEqual(loadInternalFileModule.value(), 111); + + const extraModuleName = 'extra-module'; + assert.strictEqual( + loadInternalFileModule.extraName(), 'patched-' + extraModuleName); + assert.strictEqual(loadInternalFileModule.extraValue(), 121); + }); + + it('should not load a non existing plugin and just log an erro', () => { const intercept = require('intercept-stdout'); @@ -129,7 +154,7 @@ describe('Plugin Loader', () => { }); }); - /** Should load/unload end-user (non-default named) plugin. */ + // Should load/unload end-user (non-default named) plugin. describe('load/unload end-user pluging', () => { it('should load/unload patch/unpatch end-user plugins', () => { const pluginLoader = new PluginLoader(log, tracer);