Repository: honeycombio/beeline-nodejs
Pull request #114 (2019-01-15 21:36:34)

support disabling instrumentations at startup
Add a configure argument `enabledInstrumentations` and a function `beeline.getInstrumentations()` that can be used to tailor the list of active instrumentations.

e.g. to enable _only_ mongodb:

```
beeline({
  enabledInstrumentations: ["mongodb"]
});
```

and to enable all instrumentations _but_ mongodb:

```
beeline({
  enabledInstrumentations: beeline.getInstrumentations().filter(i => i !== "mongodb")
});
```

This now gives us two ways to disable all instrumentation.

```
beeline({
  disableInstrumentation: true,
  enabledInstrumentations: []
});
```

Dropping `disableInstrumentation` on the next major bump would probably be a good thing.


Patch:
diff --git a/lib/index.js b/lib/index.js
index 6b3231e..3491ded 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -18,4 +18,6 @@ Object.keys(api).forEach(k => {
   configure[k] = api[k];
 });
 
+configure.getInstrumentations = instrumentation.getInstrumentations;
+
 module.exports = configure;
diff --git a/lib/instrumentation.js b/lib/instrumentation.js
index 9ce9c22..396987c 100644
--- a/lib/instrumentation.js
+++ b/lib/instrumentation.js
@@ -7,7 +7,7 @@ const shimmer = require("shimmer"),
   pkg = require(path.join(__dirname, "..", "package.json")),
   debug = require("debug")(`${pkg.name}:instrumentation`);
 
-const instrumentedModules = new Set([
+const instrumentations = [
   "express",
   "react-dom/server",
   "mysql2",
@@ -19,7 +19,9 @@ const instrumentedModules = new Set([
   "bluebird",
   "sequelize",
   "child_process",
-]);
+];
+
+let enabledInstrumentations = new Set(instrumentations);
 const instrumentedPaths = new Map();
 const instrumentationsActive = new Set();
 exports.activeInstrumentations = () => Array.from(instrumentationsActive.keys()).sort();
@@ -73,7 +75,7 @@ function getPackageVersion(request, options) {
 }
 
 const instrumentLoad = (exports.instrumentLoad = (mod, loadRequest, parent, opts = {}) => {
-  if (parent.id.includes(`node_modules/${pkg.name}`) || !instrumentedModules.has(loadRequest)) {
+  if (parent.id.includes(`node_modules/${pkg.name}`) || !enabledInstrumentations.has(loadRequest)) {
     // no magic here
     return mod;
   }
@@ -117,7 +119,7 @@ const instrumentLoad = (exports.instrumentLoad = (mod, loadRequest, parent, opts
 const checkForAlreadyRequiredModules = () => {
   let modulesRequired = [];
 
-  for (let m of instrumentedModules.values()) {
+  for (let m of enabledInstrumentations.values()) {
     try {
       // try to resolve our known modules in the context of the main module
       let resolvedPath = require.resolve(m, { paths: require.main.paths });
@@ -144,6 +146,11 @@ exports.configure = (opts = {}) => {
   if (opts.disableInstrumentation) {
     return;
   }
+
+  if (opts.enabledInstrumentations !== undefined) {
+    enabledInstrumentations = new Set(opts.enabledInstrumentations);
+  }
+
   if (!opts.disableInstrumentationOnLoad) {
     checkForAlreadyRequiredModules();
 
@@ -157,7 +164,10 @@ exports.configure = (opts = {}) => {
   instrumentPreload();
 };
 
+exports.getInstrumentations = () => instrumentations.slice();
+
 exports.clearInstrumentationForTesting = () => {
+  enabledInstrumentations = new Set(instrumentations);
   instrumentationsActive.clear();
   instrumentedPaths.clear();
 };


Test patch:
diff --git a/lib/instrumentation.test.js b/lib/instrumentation.test.js
index 1bd9c13..03048e2 100644
--- a/lib/instrumentation.test.js
+++ b/lib/instrumentation.test.js
@@ -53,6 +53,33 @@ test("instrumentation is keyed off the requested module name, not the module its
   expect(m.__wrapped).toBeUndefined();
 });
 
+test("if an instrumentation is disabled, don't instrument the module", () => {
+  instrumentation.clearInstrumentationForTesting();
+  instrumentation.configure({ enabledInstrumentations: [], disableInstrumentationOnLoad: true });
+  let fakeExpress = createFakeExpress();
+  let m = instrumentation.instrumentLoad(fakeExpress, "express", {
+    id: "foo/stuff/node_modules/",
+  });
+
+  expect(m).toBe(fakeExpress);
+  expect(m.__wrapped).toBeUndefined();
+});
+
+test("if an instrumentation is enabled, instrument the module", () => {
+  instrumentation.clearInstrumentationForTesting();
+  instrumentation.configure({
+    enabledInstrumentations: ["express"],
+    disableInstrumentationOnLoad: true,
+  });
+  let fakeExpress = createFakeExpress();
+  let m = instrumentation.instrumentLoad(fakeExpress, "express", {
+    id: "foo/stuff/node_modules/",
+  });
+
+  expect(m).not.toBe(fakeExpress);
+  expect(m.__wrapped).toBe(true);
+});
+
 test("we keep track of the active instrumentations in a sorted array", () => {
   instrumentation.clearInstrumentationForTesting();
   let fakeExpress = createFakeExpress();