Skip to content

Commit

Permalink
feat: add OPENTELEMETRY_NO_PATCH_MODULES (#1153)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
  • Loading branch information
markwolff and dyladan committed Jun 12, 2020
1 parent e9b2cf9 commit 8c56442
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
8 changes: 8 additions & 0 deletions packages/opentelemetry-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ const provider = new NodeTracerProvider({
});
```

### Disable Plugins with Environment Variables

Plugins can be disabled without modifying and redeploying code.
`OTEL_NO_PATCH_MODULES` accepts a
comma separated list of module names to disabled specific plugins.
The names should match what you use to `require` the module into your application.
For example, `OTEL_NO_PATCH_MODULES=pg,https` will disable the postgres plugin and the https plugin. To disable **all** plugins, set the environment variable to `*`.

## Examples

See how to automatically instrument [http](https://github.com/open-telemetry/opentelemetry-js/tree/master/examples/http) and [gRPC](https://github.com/open-telemetry/opentelemetry-js/tree/master/examples/grpc) using node-sdk.
Expand Down
39 changes: 39 additions & 0 deletions packages/opentelemetry-node/src/instrumentation/PluginLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export enum HookState {
DISABLED,
}

/**
* Environment variable which will contain list of modules to not load corresponding plugins for
* e.g.OTEL_NO_PATCH_MODULES=pg,https,mongodb
*/
export const ENV_PLUGIN_DISABLED_LIST = 'OTEL_NO_PATCH_MODULES';

/**
* Wildcard symbol. If ignore list is set to this, disable all plugins
*/
const DISABLE_ALL_PLUGINS = '*';

export interface Plugins {
[pluginName: string]: PluginConfig;
}
Expand All @@ -46,6 +57,18 @@ function filterPlugins(plugins: Plugins): Plugins {
}, {});
}

/**
* Parse process.env[ENV_PLUGIN_DISABLED_LIST] for a list of modules
* not to load corresponding plugins for.
*/
function getIgnoreList(): string[] | typeof DISABLE_ALL_PLUGINS {
const envIgnoreList: string = process.env[ENV_PLUGIN_DISABLED_LIST] || '';
if (envIgnoreList === DISABLE_ALL_PLUGINS) {
return envIgnoreList;
}
return envIgnoreList.split(',').map(v => v.trim());
}

/**
* The PluginLoader class can load instrumentation plugins that use a patch
* mechanism to enable automatic tracing for specific target modules.
Expand Down Expand Up @@ -74,6 +97,7 @@ export class PluginLoader {
if (this._hookState === HookState.UNINITIALIZED) {
const pluginsToLoad = filterPlugins(plugins);
const modulesToHook = Object.keys(pluginsToLoad);
const modulesToIgnore = getIgnoreList();
// 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
Expand Down Expand Up @@ -119,6 +143,21 @@ export class PluginLoader {
version = utils.getPackageVersion(this.logger, baseDir);
}

// Skip loading of all modules if '*' is provided
if (modulesToIgnore === DISABLE_ALL_PLUGINS) {
this.logger.info(
`PluginLoader#load: skipped patching module ${name} because all plugins are disabled (${ENV_PLUGIN_DISABLED_LIST})`
);
return exports;
}

if (modulesToIgnore.includes(name)) {
this.logger.info(
`PluginLoader#load: skipped patching module ${name} because it was on the ignore list (${ENV_PLUGIN_DISABLED_LIST})`
);
return exports;
}

this.logger.info(
`PluginLoader#load: trying to load ${name}@${version}`
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
PluginLoader,
Plugins,
searchPathForTest,
ENV_PLUGIN_DISABLED_LIST,
} from '../../src/instrumentation/PluginLoader';

const INSTALLED_PLUGINS_PATH = path.join(__dirname, 'node_modules');
Expand Down Expand Up @@ -135,6 +136,10 @@ describe('PluginLoader', () => {
});

describe('.load()', () => {
afterEach(() => {
delete process.env[ENV_PLUGIN_DISABLED_LIST];
});

it('sanity check', () => {
// Ensure that module fixtures contain values that we expect.
const simpleModule = require('simple-module');
Expand All @@ -152,6 +157,86 @@ describe('PluginLoader', () => {
assert.throws(() => require('nonexistent-module'));
});

it('should not load a plugin on the ignore list environment variable', () => {
// Set ignore list env var
process.env[ENV_PLUGIN_DISABLED_LIST] = 'simple-module';
const pluginLoader = new PluginLoader(provider, logger);
pluginLoader.load({ ...simplePlugins, ...supportedVersionPlugins });

assert.strictEqual(pluginLoader['_plugins'].length, 0);

const simpleModule = require('simple-module');
assert.strictEqual(pluginLoader['_plugins'].length, 0);
assert.strictEqual(simpleModule.value(), 0);
assert.strictEqual(simpleModule.name(), 'simple-module');

const supportedModule = require('supported-module');
assert.strictEqual(pluginLoader['_plugins'].length, 1);
assert.strictEqual(supportedModule.value(), 1);
assert.strictEqual(supportedModule.name(), 'patched-supported-module');

pluginLoader.unload();
});

it('should not load plugins on the ignore list environment variable', () => {
// Set ignore list env var
process.env[ENV_PLUGIN_DISABLED_LIST] = 'simple-module,http';
const pluginLoader = new PluginLoader(provider, logger);
pluginLoader.load({
...simplePlugins,
...supportedVersionPlugins,
...httpPlugins,
});

assert.strictEqual(pluginLoader['_plugins'].length, 0);

const simpleModule = require('simple-module');
assert.strictEqual(pluginLoader['_plugins'].length, 0);
assert.strictEqual(simpleModule.value(), 0);
assert.strictEqual(simpleModule.name(), 'simple-module');

const httpModule = require('http');
assert.ok(httpModule);
assert.strictEqual(pluginLoader['_plugins'].length, 0);

const supportedModule = require('supported-module');
assert.strictEqual(pluginLoader['_plugins'].length, 1);
assert.strictEqual(supportedModule.value(), 1);
assert.strictEqual(supportedModule.name(), 'patched-supported-module');

pluginLoader.unload();
});

it('should not load any plugins if ignore list environment variable is set to "*"', () => {
// Set ignore list env var
process.env[ENV_PLUGIN_DISABLED_LIST] = '*';
const pluginLoader = new PluginLoader(provider, logger);
pluginLoader.load({
...simplePlugins,
...supportedVersionPlugins,
...httpPlugins,
});

assert.strictEqual(pluginLoader['_plugins'].length, 0);

const simpleModule = require('simple-module');
const httpModule = require('http');
const supportedModule = require('supported-module');

assert.strictEqual(
pluginLoader['_plugins'].length,
0,
'No plugins were loaded'
);
assert.strictEqual(simpleModule.value(), 0);
assert.strictEqual(simpleModule.name(), 'simple-module');
assert.ok(httpModule);
assert.strictEqual(supportedModule.value(), 0);
assert.strictEqual(supportedModule.name(), 'supported-module');

pluginLoader.unload();
});

it('should load a plugin and patch the target modules', () => {
const pluginLoader = new PluginLoader(provider, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
Expand Down

0 comments on commit 8c56442

Please sign in to comment.