diff --git a/examples/grpc/tracer.js b/examples/grpc/tracer.js index cee15c735f..34b03b1030 100644 --- a/examples/grpc/tracer.js +++ b/examples/grpc/tracer.js @@ -9,14 +9,7 @@ const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin'); const EXPORTER = process.env.EXPORTER || ''; module.exports = (serviceName) => { - const provider = new NodeTracerProvider({ - plugins: { - grpc: { - enabled: true, - path: '@opentelemetry/plugin-grpc', - }, - }, - }); + const provider = new NodeTracerProvider(); let exporter; if (EXPORTER.toLowerCase().startsWith('z')) { diff --git a/packages/opentelemetry-node/README.md b/packages/opentelemetry-node/README.md index 4dd8d84a74..df932e2ed1 100644 --- a/packages/opentelemetry-node/README.md +++ b/packages/opentelemetry-node/README.md @@ -1,4 +1,5 @@ # OpenTelemetry Node SDK + [![Gitter chat][gitter-image]][gitter-url] [![NPM Published Version][npm-img]][npm-url] [![dependencies][dependencies-image]][dependencies-url] @@ -10,14 +11,14 @@ This module provides *automated instrumentation and tracing* for Node.js applica For manual instrumentation see the [@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing) package. -## How does automated instrumentation work? +## How auto instrumentation works + This package exposes a `NodeTracerProvider` that will automatically hook into the module loader of Node.js. For this to work, please make sure that `NodeTracerProvider` is initialized before any other module of your application, (like `http` or `express`) is loaded. OpenTelemetry comes with a growing number of instrumentation plugins for well know modules (see [supported modules](https://github.com/open-telemetry/opentelemetry-js#plugins)) and an API to create custom plugins (see [the plugin developer guide](https://github.com/open-telemetry/opentelemetry-js/blob/master/doc/plugin-guide.md)). - Whenever a module is loaded `NodeTracerProvider` will check if a matching instrumentation plugin has been installed. > **Please note:** This module does *not* bundle any plugins. They need to be installed separately. @@ -26,6 +27,7 @@ If the respective plugin was found, it will be used to patch the original module This is done by wrapping all tracing-relevant functions. This instrumentation code will automatically + - extract a trace-context identifier from inbound requests to allow distributed tracing (if applicable) - make sure that this current trace-context is propagated while the transaction traverses an application (see [@opentelemetry/context-base](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-context-base/README.md) for an in-depth explanation) - add this trace-context identifier to outbound requests to allow continuing the distributed trace on the next hop (if applicable) @@ -34,6 +36,7 @@ This instrumentation code will automatically In short, this means that this module will use provided plugins to automatically instrument your application to produce spans and provide end-to-end tracing by just adding a few lines of code. ## Creating custom spans on top of auto-instrumentation + Additionally to automated instrumentation, `NodeTracerProvider` exposes the same API as [@opentelemetry/tracing](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-tracing), allowing creating custom spans if needed. ## Installation @@ -49,22 +52,16 @@ npm install --save @opentelemetry/plugin-https ## Usage -The following code will configure the `NodeTracerProvider` to instrument `http` using `@opentelemetry/plugin-http`. +The following code will configure the `NodeTracerProvider` to instrument `http` +(and any other installed [supported +modules](https://github.com/open-telemetry/opentelemetry-js#plugins)) +using `@opentelemetry/plugin-http`. ```js const { NodeTracerProvider } = require('@opentelemetry/node'); // Create and configure NodeTracerProvider -const provider = new NodeTracerProvider({ - plugins: { - http: { - enabled: true, - // You may use a package name or absolute path to the file. - path: '@opentelemetry/plugin-http', - // http plugin options - } - } -}); +const provider = new NodeTracerProvider(); // Initialize the provider provider.register() @@ -74,26 +71,43 @@ provider.register() const http = require('http'); ``` -To enable instrumentation for all [supported modules](https://github.com/open-telemetry/opentelemetry-js#plugins), create an instance of `NodeTracerProvider` without providing any plugin configuration to the constructor. +## Plugin configuration -```js -const { NodeTracerProvider } = require('@opentelemetry/node'); +User supplied plugin configuration is merged with the default plugin +configuration. Furthermore, custom plugins that are configured are implicitly +enabled just as default plugins are. -// Create and initialize NodeTracerProvider -const provider = new NodeTracerProvider(); +In the following example: -// Initialize the provider -provider.register() +- the default express plugin is disabled +- the http plugin has a custom config for a `requestHook` +- the customPlugin is loaded from the user supplied path +- all default plugins are still loaded if installed. -// Your application code -// ... +```js +const provider = new NodeTracerProvider({ + plugins: { + express: { + enabled: false, + }, + http: { + requestHook: (span, request) => { + span.setAttribute("custom request hook attribute", "request"); + }, + }, + customPlugin: { + path: "/path/to/custom/module", + }, + }, +}); ``` ## 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. +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. ## Useful links + - For more information on OpenTelemetry, visit: - For more about OpenTelemetry JavaScript: - For help or feedback on this project, join us on [gitter][gitter-url] diff --git a/packages/opentelemetry-node/src/NodeTracerProvider.ts b/packages/opentelemetry-node/src/NodeTracerProvider.ts index 11a0874e5e..6d7f51737b 100644 --- a/packages/opentelemetry-node/src/NodeTracerProvider.ts +++ b/packages/opentelemetry-node/src/NodeTracerProvider.ts @@ -20,7 +20,7 @@ import { SDKRegistrationConfig, } from '@opentelemetry/tracing'; import { DEFAULT_INSTRUMENTATION_PLUGINS, NodeTracerConfig } from './config'; -import { PluginLoader } from './instrumentation/PluginLoader'; +import { PluginLoader, Plugins } from './instrumentation/PluginLoader'; /** * Register this TracerProvider for use with the OpenTelemetry API. @@ -39,7 +39,12 @@ export class NodeTracerProvider extends BasicTracerProvider { super(config); this._pluginLoader = new PluginLoader(this, this.logger); - this._pluginLoader.load(config.plugins || DEFAULT_INSTRUMENTATION_PLUGINS); + + config.plugins + ? this._pluginLoader.load( + this._mergePlugins(DEFAULT_INSTRUMENTATION_PLUGINS, config.plugins) + ) + : this._pluginLoader.load(DEFAULT_INSTRUMENTATION_PLUGINS); } stop() { @@ -54,4 +59,33 @@ export class NodeTracerProvider extends BasicTracerProvider { super.register(config); } + + /** + * Two layer merge. + * First, for user supplied config of plugin(s) that are loaded by default, + * merge the user supplied and default configs of said plugin(s). + * Then merge the results with the default plugins. + * @returns 2-layer deep merge of default and user supplied plugins. + */ + private _mergePlugins( + defaultPlugins: Plugins, + userSuppliedPlugins: Plugins + ): Plugins { + const mergedUserSuppliedPlugins: Plugins = {}; + + for (const pluginName in userSuppliedPlugins) { + mergedUserSuppliedPlugins[pluginName] = { + // Any user-supplied non-default plugin should be enabled by default + ...(DEFAULT_INSTRUMENTATION_PLUGINS[pluginName] || { enabled: true }), + ...userSuppliedPlugins[pluginName], + }; + } + + const mergedPlugins: Plugins = { + ...defaultPlugins, + ...mergedUserSuppliedPlugins, + }; + + return mergedPlugins; + } } diff --git a/packages/opentelemetry-node/test/NodeTracerProvider.test.ts b/packages/opentelemetry-node/test/NodeTracerProvider.test.ts index d38dda5319..c9594e9288 100644 --- a/packages/opentelemetry-node/test/NodeTracerProvider.test.ts +++ b/packages/opentelemetry-node/test/NodeTracerProvider.test.ts @@ -81,29 +81,38 @@ describe('NodeTracerProvider', () => { assert.ok(provider instanceof NodeTracerProvider); }); - it('should load user configured plugins', () => { + it('should load a merge of user configured and default plugins and implictly enable non-default plugins', () => { provider = new NodeTracerProvider({ logger: new NoopLogger(), plugins: { 'simple-module': { - enabled: true, path: '@opentelemetry/plugin-simple-module', }, 'supported-module': { - enabled: true, path: '@opentelemetry/plugin-supported-module', enhancedDatabaseReporting: false, ignoreMethods: [], ignoreUrls: [], }, + 'random-module': { + enabled: false, + path: '@opentelemetry/random-module', + }, + http: { + path: '@opentelemetry/plugin-http-module', + }, }, }); - const pluginLoader = provider['_pluginLoader']; - assert.strictEqual(pluginLoader['_plugins'].length, 0); + const plugins = provider['_pluginLoader']['_plugins']; + assert.strictEqual(plugins.length, 0); require('simple-module'); - assert.strictEqual(pluginLoader['_plugins'].length, 1); + assert.strictEqual(plugins.length, 1); require('supported-module'); - assert.strictEqual(pluginLoader['_plugins'].length, 2); + assert.strictEqual(plugins.length, 2); + require('random-module'); + assert.strictEqual(plugins.length, 2); + require('http'); + assert.strictEqual(plugins.length, 3); }); it('should construct an instance with default attributes', () => { @@ -269,3 +278,52 @@ describe('NodeTracerProvider', () => { }); }); }); + +describe('mergePlugins', () => { + const defaultPlugins = { + module1: { + enabled: true, + path: 'testpath', + }, + module2: { + enabled: true, + path: 'testpath2', + }, + module3: { + enabled: true, + path: 'testpath3', + }, + }; + + const userPlugins = { + module2: { + path: 'userpath', + }, + module3: { + enabled: false, + }, + nonDefaultModule: { + path: 'userpath2', + }, + }; + + const provider = new NodeTracerProvider(); + + const mergedPlugins = provider['_mergePlugins'](defaultPlugins, userPlugins); + + it('should merge user and default configs', () => { + assert.equal(mergedPlugins.module1.enabled, true); + assert.equal(mergedPlugins.module1.path, 'testpath'); + assert.equal(mergedPlugins.module2.enabled, true); + assert.equal(mergedPlugins.module2.path, 'userpath'); + assert.equal(mergedPlugins.module3.enabled, false); + assert.equal(mergedPlugins.nonDefaultModule.enabled, true); + assert.equal(mergedPlugins.nonDefaultModule.path, 'userpath2'); + }); + + it('should should not mangle default config', () => { + assert.equal(defaultPlugins.module2.path, 'testpath2'); + assert.equal(defaultPlugins.module3.enabled, true); + assert.equal(defaultPlugins.module3.path, 'testpath3'); + }); +}); diff --git a/packages/opentelemetry-node/test/instrumentation/node_modules/@opentelemetry/plugin-http-module/package.json b/packages/opentelemetry-node/test/instrumentation/node_modules/@opentelemetry/plugin-http-module/package.json index 59d87df350..bb40eab67d 100644 --- a/packages/opentelemetry-node/test/instrumentation/node_modules/@opentelemetry/plugin-http-module/package.json +++ b/packages/opentelemetry-node/test/instrumentation/node_modules/@opentelemetry/plugin-http-module/package.json @@ -1,4 +1,4 @@ { - "name": "@opentelemetry/plugin-simple-module", + "name": "@opentelemetry/plugin-http-module", "version": "0.0.1" }