Repository: brightcove/hot-shots
Pull request #105 (2019-04-09 10:18:25)

add env vars support for dogstatsd host, port and entity id
Support of 3 environment variables for DogStatsD Client configuration:

`"DD_AGENT_HOST"` used to provide the Agent hostname.
`"DD_DOGSTATSD_PORT"` used to provide the DogStatsD port.
`"DD_ENTITY_ID"` used to provide an identifier to the Client.

If Client `host` and `port` constructor's arguments are not set, prioritize `"DD_AGENT_HOST"` and `"DD_DOGSTATSD_PORT"` over default values.
Add `"DD_ENTITY_ID"` as a global tag with the tag name `dd.internal.entity_id`.


Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index 91d2351..1d9ec12 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -167,7 +167,6 @@ module.exports = {
         "no-param-reassign": "off",
         "no-path-concat": "error",
         "no-plusplus": "off",
-        "no-process-env": "error",
         "no-process-exit": "error",
         "no-proto": "error",
         "no-prototype-builtins": "off",
diff --git a/README.md b/README.md
index bfd3183..2a18aed 100644
--- a/README.md
+++ b/README.md
@@ -28,14 +28,14 @@ You may also want to use the Datadog events support in here instead of other lib
 All initialization parameters are optional.
 
 Parameters (specified as one object passed into hot-shots):
-* `host`:        The host to send stats to `default: localhost`
-* `port`:        The port to send stats to `default: 8125`
+* `host`:        The host to send stats to, if not set, the constructor tries to retrieve it from the `DD_AGENT_HOST` environment variable, `default: localhost`
+* `port`:        The port to send stats to, if not set, the constructor tries to retrieve it from the `DD_DOGSTATSD_PORT` environment variable, `default: 8125`
 * `prefix`:      What to prefix each stat name with `default: ''`
 * `suffix`:      What to suffix each stat name with `default: ''`
 * `globalize`:   Expose this StatsD instance globally? `default: false`
 * `cacheDns`:    Cache the initial dns lookup to *host* `default: false`
 * `mock`:        Create a mock StatsD instance, sending no stats to the server and allowing data to be read from mockBuffer? `default: false`
-* `globalTags`:  Tags that will be added to every metric. Can be either an object or list of tags. `default: {}`
+* `globalTags`:  Tags that will be added to every metric. Can be either an object or list of tags. The *Datadog* `dd.internal.entity_id` tag is appended to `globalTags` from the `DD_ENTITY_ID` environment variable if the latter is set. `default: {}`
 * `maxBufferSize`: If larger than 0,  metrics will be buffered and only sent when the string length is greater than the size. `default: 0`
 * `bufferFlushInterval`: If buffering is in use, this is the time in ms to always flush any buffered metrics. `default: 1000`
 * `telegraf`:    Use Telegraf's StatsD line protocol, which is slightly different than the rest `default: false`
diff --git a/lib/helpers.js b/lib/helpers.js
index 5584c9f..7903de3 100644
--- a/lib/helpers.js
+++ b/lib/helpers.js
@@ -110,5 +110,6 @@ module.exports = {
   formatTags: formatTags,
   overrideTags: overrideTags,
   formatDate: formatDate,
-  getDefaultRoute: getDefaultRoute
+  getDefaultRoute: getDefaultRoute,
+  sanitizeTags: sanitizeTags
 };
diff --git a/lib/statsd.js b/lib/statsd.js
index d738023..09f321a 100644
--- a/lib/statsd.js
+++ b/lib/statsd.js
@@ -61,8 +61,8 @@ const Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
   options.globalTags = options.globalTags || options.global_tags;
 
   this.protocol    = (options.protocol && options.protocol.toLowerCase());
-  this.host        = options.host || 'localhost';
-  this.port        = options.port || 8125;
+  this.host        = options.host || process.env.DD_AGENT_HOST || 'localhost';
+  this.port        = options.port || parseInt(process.env.DD_DOGSTATSD_PORT, 10) || 8125;
   this.prefix      = options.prefix || '';
   this.suffix      = options.suffix || '';
   this.socket      = options.isChild ? options.socket : createSocket(this, {
@@ -73,6 +73,12 @@ const Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
   this.mock        = options.mock;
   this.globalTags  = typeof options.globalTags === 'object' ?
       helpers.formatTags(options.globalTags, options.telegraf) : [];
+  if (process.env.DD_ENTITY_ID) {
+    this.globalTags = this.globalTags.filter((item) => {
+      return item.indexOf('dd.internal.entity_id:') !== 0;
+   });
+    this.globalTags.push('dd.internal.entity_id:'.concat(helpers.sanitizeTags(process.env.DD_ENTITY_ID)));
+  }
   this.telegraf    = options.telegraf || false;
   this.maxBufferSize = options.maxBufferSize || 0;
   this.sampleRate  = options.sampleRate || 1;
diff --git a/package-lock.json b/package-lock.json
index 67878bd..dd3499f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -782,6 +782,7 @@
           "version": "0.1.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "kind-of": "^3.0.2",
             "longest": "^1.0.1",
@@ -1964,7 +1965,8 @@
         "longest": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "loose-envify": {
           "version": "1.3.1",


Test patch:
diff --git a/test/globalTags.js b/test/globalTags.js
index 857be0f..e34ee21 100644
--- a/test/globalTags.js
+++ b/test/globalTags.js
@@ -12,6 +12,7 @@ describe('#globalTags', () => {
 
   afterEach(done => {
     closeAll(server, statsd, false, done);
+    delete process.env.DD_ENTITY_ID;
   });
 
   testTypes().forEach(([description, serverType, clientType, metricEnd]) => {
@@ -47,6 +48,25 @@ describe('#globalTags', () => {
         });
       });
 
+      it('should add dd.internal.entity_id tag from DD_ENTITY_ID env var', done => {
+        // set the DD_ENTITY_ID env var
+        process.env.DD_ENTITY_ID = '04652bb7-19b7-11e9-9cc6-42010a9c016d';
+
+        server = createServer(serverType, address => {
+          statsd = createHotShotsClient({
+            host: address.address,
+            port: address.port,
+            global_tags: ['gtag'],
+            protocol: serverType
+          }, clientType);
+          statsd.increment('test');
+        });
+        server.on('metrics', metrics => {
+          assert.equal(metrics, `test:1|c|#gtag,dd.internal.entity_id:04652bb7-19b7-11e9-9cc6-42010a9c016d${metricEnd}`);
+          done();
+        });
+      });
+
       it('should combine global tags and metric tags', done => {
         server = createServer(serverType, address => {
           statsd = createHotShotsClient({
diff --git a/test/init.js b/test/init.js
index 640c583..89806eb 100644
--- a/test/init.js
+++ b/test/init.js
@@ -25,6 +25,9 @@ describe('#init', () => {
     }
     global.statsd = undefined;
     skipClose = false;
+    delete process.env.DD_AGENT_HOST;
+    delete process.env.DD_DOGSTATSD_PORT;
+    delete process.env.DD_ENTITY_ID;
   });
 
   it('should set the proper values when specified', () => {
@@ -85,6 +88,27 @@ describe('#init', () => {
     dns.lookup = originalLookup;
   });
 
+  it('should get host and port values from env vars when not specified', () => {
+    // set the DD_AGENT_HOST and DD_DOGSTATSD_PORT env vars
+    process.env.DD_AGENT_HOST = 'envhost';
+    process.env.DD_DOGSTATSD_PORT = '1234';
+
+    statsd = createHotShotsClient({}, clientType);
+    assert.equal(statsd.host, 'envhost');
+    assert.equal(statsd.port, 1234);
+    assert.equal(statsd.prefix, '');
+    assert.equal(statsd.suffix, '');
+    assert.equal(global.statsd, undefined);
+    assert.equal(statsd.mock, undefined);
+    assert.deepEqual(statsd.globalTags, []);
+    assert.ok(!statsd.mock);
+    assert.equal(statsd.sampleRate, 1);
+    assert.equal(statsd.maxBufferSize, 0);
+    assert.equal(statsd.bufferFlushInterval, 1000);
+    assert.equal(statsd.telegraf, false);
+    assert.equal(statsd.protocol, undefined); // Defaults to UDP
+  });
+
   it('should set default values when not specified', () => {
     statsd = createHotShotsClient({}, clientType);
     assert.equal(statsd.host, 'localhost');
@@ -107,6 +131,22 @@ describe('#init', () => {
     assert.deepEqual(statsd.globalTags, ['gtag']);
   });
 
+  it('should get the dd.internal.entity_id tag from DD_ENTITY_ID env var', () => {
+    // set the DD_ENTITY_ID env var
+    process.env.DD_ENTITY_ID = '04652bb7-19b7-11e9-9cc6-42010a9c016d';
+
+    statsd = createHotShotsClient({}, clientType);
+    assert.deepEqual(statsd.globalTags, ['dd.internal.entity_id:04652bb7-19b7-11e9-9cc6-42010a9c016d']);
+  });
+
+  it('should get the dd.internal.entity_id tag from DD_ENTITY_ID env var and append it to existing tags', () => {
+    // set the DD_ENTITY_ID env var
+    process.env.DD_ENTITY_ID = '04652bb7-19b7-11e9-9cc6-42010a9c016d';
+
+    statsd = createHotShotsClient({ globalTags: ['gtag'] }, clientType);
+    assert.deepEqual(statsd.globalTags, ['gtag', 'dd.internal.entity_id:04652bb7-19b7-11e9-9cc6-42010a9c016d']);
+  });
+
   it('should attempt to cache a dns record if dnsCache is specified', done => {
     const originalLookup = dns.lookup;