UNPKG

@opencensus/nodejs-base

Version:

OpenCensus is a toolkit for collecting application performance and behavior data.

165 lines (164 loc) 6.33 kB
"use strict"; /** * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.PluginLoader = void 0; const fs = require("fs"); const path = require("path"); const hook = require("require-in-the-middle"); const constants_1 = require("../constants"); var HookState; (function (HookState) { HookState[HookState["UNINITIALIZED"] = 0] = "UNINITIALIZED"; HookState[HookState["ENABLED"] = 1] = "ENABLED"; HookState[HookState["DISABLED"] = 2] = "DISABLED"; })(HookState || (HookState = {})); /** * The PluginLoader class can load instrumentation plugins that * use a patch mechanism to enable automatic tracing for * specific target modules. */ class PluginLoader { /** * Constructs a new PluginLoader instance. * @param tracer The tracer. */ constructor(logger, tracer, stats) { /** A list of loaded plugins. */ this.plugins = []; /** * A field that tracks whether the r-i-t-m hook has been loaded for the * first time, as well as whether the hook body is enabled or not. */ this.hookState = HookState.UNINITIALIZED; this.tracer = tracer; this.logger = logger; this.stats = stats; } /** * Gets the default package name for a target module. The default package * name uses the default scope and a default prefix. * @param moduleName The module name. * @returns The default name for that package. */ static defaultPackageName(moduleName) { return `${constants_1.Constants.OPENCENSUS_SCOPE}/${constants_1.Constants.DEFAULT_PLUGIN_PACKAGE_NAME_PREFIX}-${moduleName}`; } /** * Returns a PluginNames object, build from a string array of target modules * names, using the defaultPackageName. * @param modulesToPatch A list of modules to patch. * @returns Plugin names. */ static defaultPluginsFromArray(modulesToPatch) { const plugins = modulesToPatch.reduce((plugins, moduleName) => { plugins[moduleName] = PluginLoader.defaultPackageName(moduleName); return plugins; }, {}); return plugins; } /** * Gets the package version. * @param name Name. * @param basedir The base directory. */ getPackageVersion(name, basedir) { let version = null; if (basedir) { const pkgJson = path.join(basedir, 'package.json'); try { version = JSON.parse(fs.readFileSync(pkgJson).toString()).version; } catch (e) { this.logger.error('could not get version of %s module: %s', name, e.message); } } else { version = process.versions.node; } return version; } /** * Loads a list of plugins (using a map of the target module name * and its instrumentation plugin package name). Each plugin module * should implement the core Plugin interface and export an instance * named as "plugin". This function will attach a hook to be called * the first time the module is loaded. * @param pluginList A list of plugins. */ loadPlugins(pluginList) { if (this.hookState === HookState.UNINITIALIZED) { const modulesToHook = Object.keys(pluginList); // Do not hook require when no module is provided. In this case it is // not necessary. With skipping this step we lower our footprint in // customer applications and require-in-the-middle won't show up in CPU // frames. if (modulesToHook.length === 0) { this.hookState = HookState.DISABLED; return; } hook(modulesToHook, (exports, name, basedir) => { if (this.hookState !== HookState.ENABLED) { return exports; } const plugin = pluginList[name]; const version = this.getPackageVersion(name, basedir); this.logger.info('trying loading %s.%s', name, version); if (!version) { return exports; } this.logger.debug('applying patch to %s@%s module', name, version); let moduleName; let moduleConfig = {}; if (typeof plugin === 'string') { moduleName = plugin; } else { moduleConfig = plugin.config; moduleName = plugin.module; } this.logger.debug('using package %s to patch %s', moduleName, name); // Expecting a plugin from module; try { const plugin = require(moduleName).plugin; this.plugins.push(plugin); return plugin.enable(exports, this.tracer, version, moduleConfig, basedir, this.stats); } catch (e) { this.logger.error('could not load plugin %s of module %s. Error: %s', moduleName, name, e.message); return exports; } }); } this.hookState = HookState.ENABLED; } /** Unloads plugins. */ unloadPlugins() { for (const plugin of this.plugins) { plugin.disable(); } this.plugins = []; this.hookState = HookState.DISABLED; } /** * Adds a search path for plugin modules. Intended for testing purposes only. * @param searchPath The path to add. */ static set searchPathForTest(searchPath) { module.paths.push(searchPath); } } exports.PluginLoader = PluginLoader;