Comparing version 5.9.2 to 6.0.0
CHANGELOG | ||
========= | ||
## 6.0.0 (2018-12-15) | ||
@bdeitte Major upgrade to the codebase to be more modern, | ||
overhaul tests, and many small tweaks. Most of this is internal to | ||
the project, but there are a few changes to note for everyone: | ||
* Now requires Node 6 or above | ||
* Update close() to handle errors better, not doubling up in error | ||
messages and not leaving uncaught errors | ||
Everything else done here should be internal facing. Those changes | ||
include: | ||
* Use "lebab" to ES6-ify the project | ||
* Switch from jshint and eslint and make syntax updates based on this | ||
* Remove a lot of duplication in tests and many small fixups in tests | ||
* Start using Mocha 4 | ||
* Stop using index.js for testing | ||
* Start using the code coverage report as part of the build | ||
* Remove the ignoring of errors on close of tests, and tear down tests in general better | ||
* Stop using "new Buffer", that is deprecated, and use Buffer.from() instead | ||
## 5.9.2 (2018-11-10) | ||
@@ -5,0 +24,0 @@ * @stieg Add mockBuffer to types |
@@ -1,1 +0,1 @@ | ||
module.exports = require('./lib/statsd'); | ||
module.exports = require('./lib/statsd'); |
@@ -1,11 +0,15 @@ | ||
"use strict"; | ||
const fs = require('fs'); | ||
var fs = require('fs'); | ||
/** | ||
* Replace any characters that can't be sent on with an underscore | ||
*/ | ||
function sanitizeTags(value, telegraf) { | ||
var blacklist = telegraf ? /:|\|/g : /:|\||@|,/g; | ||
const blacklist = telegraf ? /:|\|/g : /:|\||@|,/g; | ||
// Replace reserved chars with underscores. | ||
return (value + "").replace(blacklist, "_"); | ||
return value.replace(blacklist, '_'); | ||
} | ||
/** | ||
* Format tags properly before sending on | ||
*/ | ||
function formatTags(tags, telegraf) { | ||
@@ -16,4 +20,4 @@ if (Array.isArray(tags)) { | ||
} else { | ||
return Object.keys(tags).map(function (key) { | ||
return sanitizeTags(key, telegraf) + ":" + sanitizeTags(tags[key], telegraf); | ||
return Object.keys(tags).map(key => { | ||
return `${sanitizeTags(key, telegraf)}:${sanitizeTags(tags[key], telegraf)}`; | ||
}); | ||
@@ -28,6 +32,6 @@ } | ||
function overrideTags (parent, child, telegraf) { | ||
var childCopy = {}; | ||
var toAppend = []; | ||
formatTags(child, telegraf).forEach(function (tag) { | ||
var idx = typeof tag === 'string' ? tag.indexOf(':') : -1; | ||
const childCopy = {}; | ||
const toAppend = []; | ||
formatTags(child, telegraf).forEach(tag => { | ||
const idx = typeof tag === 'string' ? tag.indexOf(':') : -1; | ||
if (idx < 1) { // Not found or first character | ||
@@ -39,17 +43,17 @@ toAppend.push(tag); | ||
}); | ||
var result = parent.map(function (tag) { | ||
var idx = typeof tag === 'string' ? tag.indexOf(':') : -1; | ||
const result = parent.map(tag => { | ||
const idx = typeof tag === 'string' ? tag.indexOf(':') : -1; | ||
if (idx < 1) { // Not found or first character | ||
return tag; | ||
} | ||
var key = tag.substring(0, idx); | ||
const key = tag.substring(0, idx); | ||
if (childCopy.hasOwnProperty(key)) { | ||
var value = childCopy[key]; | ||
const value = childCopy[key]; | ||
delete childCopy[key]; | ||
return key + ':' + value; | ||
return `${key}:${value}`; | ||
} | ||
return tag; | ||
}); | ||
Object.keys(childCopy).forEach(function (key) { | ||
result.push(key + ':' + childCopy[key]); | ||
Object.keys(childCopy).forEach(key => { | ||
result.push(`${key}:${childCopy[key]}`); | ||
}); | ||
@@ -59,5 +63,7 @@ return result.concat(toAppend); | ||
// Formats a date for use with DataDog | ||
/** | ||
* Formats a date for use with DataDog | ||
*/ | ||
function formatDate(date) { | ||
var timestamp; | ||
let timestamp; | ||
if (date instanceof Date) { | ||
@@ -73,23 +79,27 @@ // Datadog expects seconds. | ||
// Converts int to a string IP | ||
/** | ||
* Converts int to a string IP | ||
*/ | ||
function intToIP(int) { | ||
var part1 = int & 255; | ||
var part2 = ((int >> 8) & 255); | ||
var part3 = ((int >> 16) & 255); | ||
var part4 = ((int >> 24) & 255); | ||
const part1 = int & 255; | ||
const part2 = ((int >> 8) & 255); | ||
const part3 = ((int >> 16) & 255); | ||
const part4 = ((int >> 24) & 255); | ||
return part4 + "." + part3 + "." + part2 + "." + part1; | ||
return `${part4}.${part3}.${part2}.${part1}`; | ||
} | ||
// Returns the system default interface on Linux | ||
/** | ||
* Returns the system default interface on Linux | ||
*/ | ||
function getDefaultRoute() { | ||
try { | ||
var fileContents = fs.readFileSync('/proc/net/route', 'utf8'); | ||
var routes = fileContents.split('\n'); | ||
for (var routeIdx in routes) { | ||
var fields = routes[routeIdx].trim().split('\t'); | ||
const fileContents = fs.readFileSync('/proc/net/route', 'utf8'); // eslint-disable-line no-sync | ||
const routes = fileContents.split('\n'); | ||
for (const routeIdx in routes) { | ||
const fields = routes[routeIdx].trim().split('\t'); | ||
if (fields[1] === '00000000') { | ||
var address = fields[2]; | ||
const address = fields[2]; | ||
// Convert to little endian by splitting every 2 digits and reversing that list | ||
var littleEndianAddress = address.match(/.{2}/g).reverse().join(""); | ||
const littleEndianAddress = address.match(/.{2}/g).reverse().join(''); | ||
return intToIP(parseInt(littleEndianAddress, 16)); | ||
@@ -96,0 +106,0 @@ } |
@@ -1,32 +0,16 @@ | ||
'use strict'; | ||
const dgram = require('dgram'), | ||
util = require('util'), | ||
dns = require('dns'), | ||
net = require('net'), | ||
helpers = require('./helpers'), | ||
applyStatsFns = require('./statsFunctions'); | ||
var dgram = require('dgram'), | ||
util = require('util'), | ||
dns = require('dns'), | ||
net = require('net'), | ||
helpers = require('./helpers'), | ||
applyStatsFns = require('./statsFunctions'); | ||
/** | ||
* The UDP Client for StatsD | ||
* @param options | ||
* @option host {String} The host to connect to default: localhost | ||
* @option port {String|Integer} The port to connect to default: 8125 | ||
* @option prefix {String} An optional prefix to assign to each stat name sent | ||
* @option suffix {String} An optional suffix to assign to each stat name sent | ||
* @option globalize {boolean} An optional boolean to add 'statsd' as an object in the global namespace | ||
* @option cacheDns {boolean} An optional option to only lookup the hostname -> ip address once | ||
* @option mock {boolean} An optional boolean indicating this Client is a mock object, no stats are sent. | ||
* @option globalTags {Array=} Optional tags that will be added to every metric | ||
* @option errorHandler {Function=} Optional function to handle errors when callback is not provided | ||
* @maxBufferSize {Number} An optional value for aggregating metrics to send, mainly for performance improvement | ||
* @bufferFlushInterval {Number} the time out value to flush out buffer if not | ||
* @option sampleRate {Float} Global Sampling rate, default: 1 (No sampling) | ||
* @option useDefaultRoute {boolean} An optional boolean to use the default route on Linux. Useful for containers | ||
* The Client for StatsD | ||
* @constructor | ||
*/ | ||
var Client = function (host, port, prefix, suffix, globalize, cacheDns, mock, | ||
const Client = function (host, port, prefix, suffix, globalize, cacheDns, mock, | ||
globalTags, maxBufferSize, bufferFlushInterval, telegraf, sampleRate, protocol) { | ||
var options = host || {}, | ||
self = this; | ||
let options = host || {}; | ||
const self = this; | ||
@@ -51,5 +35,5 @@ if (arguments.length > 1 || typeof(host) === 'string') { | ||
var createSocket = function createSocket(instance, args) { | ||
var socket; | ||
var errMessage; | ||
const createSocket = function createSocket(instance, args) { | ||
let socket; | ||
let errMessage; | ||
@@ -61,3 +45,3 @@ if (args.protocol === 'tcp') { | ||
} catch (e) { | ||
errMessage = 'Could not establish connection to ' + args.host + ':' + args.port; | ||
errMessage = `Could not establish connection to ${args.host}:${args.port}`; | ||
if (instance.errorHandler) { | ||
@@ -114,3 +98,3 @@ instance.errorHandler(new Error(errMessage)); | ||
} else if (options.cacheDns === true) { | ||
dns.lookup(options.host, function (err, address, family) { | ||
dns.lookup(options.host, (err, address) => { | ||
if (err === null) { | ||
@@ -133,5 +117,5 @@ self.host = address; | ||
if (options.useDefaultRoute) { | ||
var defaultRoute = helpers.getDefaultRoute(); | ||
const defaultRoute = helpers.getDefaultRoute(); | ||
if (defaultRoute) { | ||
console.log('Got ' + defaultRoute + ' for the system\'s default route'); | ||
console.log(`Got ${defaultRoute} for the system's default route`); | ||
this.host = defaultRoute; | ||
@@ -161,6 +145,6 @@ } | ||
Client.prototype.sendAll = function (stat, value, type, sampleRate, tags, callback) { | ||
var completed = 0, | ||
calledback = false, | ||
sentBytes = 0, | ||
self = this; | ||
let completed = 0; | ||
let calledback = false; | ||
let sentBytes = 0; | ||
const self = this; | ||
@@ -210,3 +194,3 @@ if (sampleRate && typeof sampleRate !== 'number') { | ||
if (Array.isArray(stat)) { | ||
stat.forEach(function (item) { | ||
stat.forEach(item => { | ||
self.sendStat(item, value, type, sampleRate, tags, onSend); | ||
@@ -229,3 +213,3 @@ }); | ||
Client.prototype.sendStat = function (stat, value, type, sampleRate, tags, callback) { | ||
var message = this.prefix + stat + this.suffix + ':' + value + '|' + type; | ||
let message = `${this.prefix + stat + this.suffix}:${value}|${type}`; | ||
@@ -235,3 +219,3 @@ sampleRate = sampleRate || this.sampleRate; | ||
if (Math.random() < sampleRate) { | ||
message += '|@' + sampleRate; | ||
message += `|@${sampleRate}`; | ||
} else { | ||
@@ -252,3 +236,3 @@ // don't want to send if we don't meet the sample ratio | ||
Client.prototype.send = function (message, tags, callback) { | ||
var mergedTags = this.globalTags; | ||
let mergedTags = this.globalTags; | ||
if (tags && typeof tags === 'object') { | ||
@@ -260,5 +244,5 @@ mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf); | ||
message = message.split(':'); | ||
message = message[0] + ',' + mergedTags.join(',').replace(/:/g, '=') + ':' + message.slice(1).join(':'); | ||
message = `${message[0]},${mergedTags.join(',').replace(/:/g, '=')}:${message.slice(1).join(':')}`; | ||
} else { | ||
message += '|#' + mergedTags.join(','); | ||
message += `|#${mergedTags.join(',')}`; | ||
} | ||
@@ -350,3 +334,3 @@ } | ||
var buf = new Buffer(message); | ||
const buf = Buffer.from(message); | ||
try { | ||
@@ -359,3 +343,3 @@ if (this.protocol === 'tcp') { | ||
} catch (err) { | ||
var errMessage = 'Error sending hot-shots message: ' + err; | ||
const errMessage = `Error sending hot-shots message: ${err}`; | ||
if (callback) { | ||
@@ -388,6 +372,37 @@ callback(new Error(errMessage)); | ||
// error function to use in callback and catch below | ||
let handledError = false; | ||
const handleErr = (err) => { | ||
const errMessage = `Error closing hot-shots socket: ${err}`; | ||
if (handledError) { | ||
console.log(errMessage); | ||
} | ||
else { | ||
// The combination of catch and error can lead to some errors | ||
// showing up twice. So we just show one of the errors that occur | ||
// on close. | ||
handledError = true; | ||
if (callback) { | ||
callback(new Error(errMessage)); | ||
} else if (this.errorHandler) { | ||
this.errorHandler(new Error(errMessage)); | ||
} else { | ||
console.log(errMessage); | ||
} | ||
} | ||
}; | ||
if (this.errorHandler) { | ||
this.socket.removeListener('error', this.errorHandler); | ||
} | ||
// handle error and close events | ||
this.socket.on('error', handleErr); | ||
if (callback) { | ||
// use the close event rather than adding a callback to close() | ||
// because that API is not available in older Node versions | ||
this.socket.on('close', callback); | ||
this.socket.on('close', err => { | ||
if (! handledError && callback) { | ||
callback(err); | ||
} | ||
}); | ||
} | ||
@@ -402,14 +417,7 @@ | ||
} catch (err) { | ||
var errMessage = 'Error closing hot-shots socket: ' + err; | ||
if (callback) { | ||
callback(new Error(errMessage)); | ||
} else if (this.errorHandler) { | ||
this.errorHandler(new Error(errMessage)); | ||
} else { | ||
console.log(errMessage); | ||
} | ||
handleErr(err); | ||
} | ||
}; | ||
var ChildClient = function (parent, options) { | ||
const ChildClient = function (parent, options) { | ||
options = options || {}; | ||
@@ -416,0 +424,0 @@ Client.call(this, { |
@@ -1,6 +0,9 @@ | ||
"use strict"; | ||
const helpers = require('./helpers'); | ||
var helpers = require('./helpers'); | ||
/** | ||
* Separated out of statsd.js for clarity, these are the timing and other stats functions that are what are called the most | ||
* when using hot-shots | ||
*/ | ||
function applyStatsFns (Client) { | ||
function applyStatsFns (Client) { | ||
/** | ||
@@ -27,14 +30,14 @@ * Represents the timing stat | ||
Client.prototype.timer = function (func, stat, sampleRate, tags, callback) { | ||
var _this = this; | ||
const _this = this; | ||
return function () { | ||
var start = process.hrtime(); | ||
return (...args) => { | ||
const start = process.hrtime(); | ||
try { | ||
return func.apply(null, arguments); | ||
return func(...args); | ||
} finally { | ||
// get duration in milliseconds | ||
var durationComponents = process.hrtime(start); | ||
var seconds = durationComponents[0]; | ||
var nanoseconds = durationComponents[1]; | ||
var duration = seconds * 1000 + nanoseconds / 1E6; | ||
const durationComponents = process.hrtime(start); | ||
const seconds = durationComponents[0]; | ||
const nanoseconds = durationComponents[1]; | ||
const duration = (seconds * 1000) + (nanoseconds / 1E6); | ||
@@ -65,7 +68,7 @@ _this.timing( | ||
Client.prototype.asyncTimer = function (func, stat, sampleRate, tags, callback) { | ||
var self = this; | ||
return function() { | ||
var end = hrtimer(); | ||
var p = func.apply(null, arguments); | ||
var recordStat = function() { self.timing(stat, end(), sampleRate, tags, callback); }; | ||
const self = this; | ||
return (...args) => { | ||
const end = hrtimer(); | ||
const p = func(...args); | ||
const recordStat = () => { self.timing(stat, end(), sampleRate, tags, callback); }; | ||
p.then(recordStat, recordStat); | ||
@@ -76,10 +79,13 @@ return p; | ||
/** | ||
* High-resolution timer | ||
*/ | ||
function hrtimer() { | ||
var start = process.hrtime(); | ||
const start = process.hrtime(); | ||
return function () { | ||
var durationComponents = process.hrtime(start); | ||
var seconds = durationComponents[0]; | ||
var nanoseconds = durationComponents[1]; | ||
var duration = seconds * 1000 + nanoseconds / 1E6; | ||
return () => { | ||
const durationComponents = process.hrtime(start); | ||
const seconds = durationComponents[0]; | ||
const nanoseconds = durationComponents[1]; | ||
const duration = (seconds * 1000) + (nanoseconds / 1E6); | ||
return duration; | ||
@@ -188,3 +194,3 @@ }; | ||
if (this.telegraf) { | ||
var err = new Error('Not supported by Telegraf / InfluxDB'); | ||
const err = new Error('Not supported by Telegraf / InfluxDB'); | ||
if (callback) { | ||
@@ -200,21 +206,20 @@ return callback(err); | ||
var check = ['_sc', this.prefix + name + this.suffix, status], | ||
metadata = options || {}; | ||
const check = ['_sc', this.prefix + name + this.suffix, status], metadata = options || {}; | ||
if (metadata.date_happened) { | ||
var timestamp = helpers.formatDate(metadata.date_happened); | ||
const timestamp = helpers.formatDate(metadata.date_happened); | ||
if (timestamp) { | ||
check.push('d:' + timestamp); | ||
check.push(`d:${timestamp}`); | ||
} | ||
} | ||
if (metadata.hostname) { | ||
check.push('h:' + metadata.hostname); | ||
check.push(`h:${metadata.hostname}`); | ||
} | ||
var mergedTags = this.globalTags; | ||
if (tags && typeof(tags) === "object") { | ||
let mergedTags = this.globalTags; | ||
if (tags && typeof(tags) === 'object') { | ||
mergedTags = helpers.overrideTags(mergedTags, tags, this.telegraf); | ||
} | ||
if (mergedTags.length > 0) { | ||
check.push('#' + mergedTags.join(',')); | ||
check.push(`#${mergedTags.join(',')}`); | ||
} | ||
@@ -224,11 +229,11 @@ | ||
if (metadata.message) { | ||
check.push('m:' + metadata.message); | ||
check.push(`m:${metadata.message}`); | ||
} | ||
// allow for tags to be omitted and callback to be used in its place | ||
if(typeof tags === 'function' && callback === undefined) { | ||
if (typeof tags === 'function' && callback === undefined) { | ||
callback = tags; | ||
} | ||
var message = check.join('|'); | ||
const message = check.join('|'); | ||
// Service checks are unique in that message has to be the last element in | ||
@@ -257,3 +262,3 @@ // the stat if provided, so we can't append tags like other checks. This | ||
if (this.telegraf) { | ||
var err = new Error('Not supported by Telegraf / InfluxDB'); | ||
const err = new Error('Not supported by Telegraf / InfluxDB'); | ||
if (callback) { | ||
@@ -270,5 +275,6 @@ return callback(err); | ||
// Convert to strings | ||
var message, | ||
msgTitle = String(title ? title : ''), | ||
msgText = String(text ? text : msgTitle); | ||
let message; | ||
const msgTitle = String(title ? title : ''); | ||
let msgText = String(text ? text : msgTitle); | ||
// Escape new lines (unescaping is supported by DataDog) | ||
@@ -278,3 +284,3 @@ msgText = msgText.replace(/\n/g, '\\n'); | ||
// start out the message with the event-specific title and text info | ||
message = '_e{' + msgTitle.length + ',' + msgText.length + '}:' + msgTitle + '|' + msgText; | ||
message = `_e{${msgTitle.length},${msgText.length}}:${msgTitle}|${msgText}`; | ||
@@ -284,21 +290,21 @@ // add in the event-specific options | ||
if (options.date_happened) { | ||
var timestamp = helpers.formatDate(options.date_happened); | ||
const timestamp = helpers.formatDate(options.date_happened); | ||
if (timestamp) { | ||
message += '|d:' + timestamp; | ||
message += `|d:${timestamp}`; | ||
} | ||
} | ||
if (options.hostname) { | ||
message += '|h:' + options.hostname; | ||
message += `|h:${options.hostname}`; | ||
} | ||
if (options.aggregation_key) { | ||
message += '|k:' + options.aggregation_key; | ||
message += `|k:${options.aggregation_key}`; | ||
} | ||
if (options.priority) { | ||
message += '|p:' + options.priority; | ||
message += `|p:${options.priority}`; | ||
} | ||
if (options.source_type_name) { | ||
message += '|s:' + options.source_type_name; | ||
message += `|s:${options.source_type_name}`; | ||
} | ||
if (options.alert_type) { | ||
message += '|t:' + options.alert_type; | ||
message += `|t:${options.alert_type}`; | ||
} | ||
@@ -308,3 +314,3 @@ } | ||
// allow for tags to be omitted and callback to be used in its place | ||
if(typeof tags === 'function' && callback === undefined) { | ||
if (typeof tags === 'function' && callback === undefined) { | ||
callback = tags; | ||
@@ -311,0 +317,0 @@ } |
{ | ||
"name": "hot-shots", | ||
"description": "Node.js client for StatsD, DogStatsD, and Telegraf", | ||
"version": "5.9.2", | ||
"version": "6.0.0", | ||
"author": "Steve Ivy", | ||
@@ -9,4 +9,3 @@ "types": "./types.d.ts", | ||
"Russ Bradberry <rbradberry@gmail.com>", | ||
"Brian Deitte <bdeitte@gmail.com>", | ||
"Mikhail Mazurskiy <mikhail.mazursky@gmail.com>" | ||
"Brian Deitte <bdeitte@gmail.com>" | ||
], | ||
@@ -31,8 +30,8 @@ "keywords": [ | ||
"engines": { | ||
"node": ">=0.8.0" | ||
"node": ">=6.0.0" | ||
}, | ||
"scripts": { | ||
"coverage": "nyc --reporter=lcov npm test", | ||
"test": "mocha -R spec --timeout 5000 test/index.js", | ||
"lint": "jshint lib/**.js test/**.js", | ||
"coverage": "nyc --reporter=lcov --reporter=text npm test", | ||
"test": "mocha --exit -R spec --timeout 5000 test/*.js", | ||
"lint": "eslint lib/**.js test/**.js", | ||
"pretest": "npm run lint" | ||
@@ -42,4 +41,4 @@ }, | ||
"devDependencies": { | ||
"jshint": "2.x", | ||
"mocha": "2.x", | ||
"eslint": "5.9.x", | ||
"mocha": "4.x", | ||
"nyc": "11.x" | ||
@@ -46,0 +45,0 @@ }, |
@@ -5,12 +5,17 @@ # hot-shots | ||
This project is a fork off of [node-statsd](https://github.com/sivy/node-statsd). This project includes all changes in node-statsd, all open PRs to node-statsd when possible, and some additional goodies (like Telegraf support, child clients, TypeScript types, and more). | ||
This project was originally a fork off of [node-statsd](https://github.com/sivy/node-statsd). This project | ||
includes all changes in the latest node-statsd and many additional changes, including: | ||
* TypeScript types | ||
* Telegraf support | ||
* events | ||
* child clients | ||
* tcp protocol support | ||
* mock mode | ||
* asyncTimer | ||
* much more, including many bug fixes | ||
hot-shots supports Node 6.x and higher. | ||
[![Build Status](https://secure.travis-ci.org/brightcove/hot-shots.png?branch=master)](http://travis-ci.org/brightcove/hot-shots) | ||
## Installation | ||
``` | ||
$ npm install hot-shots | ||
``` | ||
## Migrating from node-statsd | ||
@@ -137,3 +142,3 @@ | ||
client.increment(['these', 'are', 'different', 'stats']); | ||
// Incrementing with tags | ||
@@ -140,0 +145,0 @@ client.increment('my_counter', ['foo', 'bar']); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
62021
1187
246
10