├── test ├── mocha.opts ├── .eslintrc.js ├── messageCollector.js ├── FakeServer.js ├── HttpSocket.js ├── EphemeralSocket.js ├── helpers.js └── StatsDClient.js ├── .travis.yml ├── .gitignore ├── Makefile ├── .eslintrc.js ├── examples ├── trivial-test.js └── stats-generator.js ├── LICENSE ├── lib ├── helpers │ ├── wrapCallback.js │ └── getExpressMiddleware.js ├── HttpSocket.js ├── EphemeralSocket.js ├── statsd-client.js └── TCPSocket.js ├── package.json ├── README.md └── CHANGELOG.md /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --timeout 10000 2 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | - "12" 6 | - "node" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test lint 2 | 3 | all: lint test 4 | 5 | test: 6 | ./node_modules/.bin/mocha -R spec 7 | 8 | lint: 9 | ./node_modules/.bin/jshint lib/ test/ 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "indent": [ "error", 4 ], 8 | "linebreak-style": [ "error", "unix" ], 9 | //"quotes": [ "error", "single" ], 10 | "semi": [ "error", "always" ] 11 | } 12 | }; -------------------------------------------------------------------------------- /examples/trivial-test.js: -------------------------------------------------------------------------------- 1 | var sdc = require('../lib/statsd-client'), 2 | SDC = new sdc({host: '10.111.12.113', prefix: "statsd-client"}), 3 | SDCTest = SDC.getChildClient('test'); 4 | 5 | 6 | var begin = new Date(); 7 | setTimeout(function () { 8 | // Set 'statsd-client.test.gauge' 9 | SDCTest.gauge('gauge', 100 * Math.random()); 10 | 11 | // Icrement 'statsd-client.test.counter' twice 12 | SDCTest.increment('counter'); 13 | SDC.increment('test.counter'); 14 | 15 | // Set some time 16 | SDC.timing('speed', begin); 17 | 18 | // Close socket 19 | SDC.close(); 20 | }, 100 * Math.random()); 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2012, Morten Siebuhr 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /examples/stats-generator.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var args = process.argv; 3 | 4 | var SDC = require('../lib/statsd-client'), 5 | sdc = new SDC({ 6 | host: 'localhost', 7 | prefix: args[2] || 'data.generator' 8 | }), 9 | rand, 10 | time = new Date(), 11 | iterations = 0; 12 | 13 | function sendSomeData() { 14 | iterations += 1; 15 | if (iterations % 10 === 0) { 16 | process.stdout.write('\r' + ['◒', '◐', '◓', '◑'][iterations/10 % 4]); 17 | iterations = iterations >= 40 ? 0 : iterations; 18 | } 19 | 20 | rand = Math.round(Math.random() * 10); 21 | 22 | sdc.gauge('gauge' + rand, rand); 23 | sdc.counter('counter' + rand, rand); 24 | sdc.set('set' + rand, rand); 25 | sdc.timing('timer' + rand, time); 26 | 27 | time = new Date(); 28 | setTimeout(sendSomeData, rand); 29 | } 30 | 31 | sendSomeData(); 32 | -------------------------------------------------------------------------------- /lib/helpers/wrapCallback.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Return a wrapped callback function shimmed to send metrics 3 | * once it is invoked. The returned function is to be used 4 | * exactly the same way as the original callback. 5 | */ 6 | function factory(parentClient) { 7 | return function (prefix, callback, options) { 8 | options = options || {}; 9 | 10 | var client = parentClient.getChildClient(prefix); 11 | var startTime = new Date(); 12 | var tags = options.tags || {}; 13 | 14 | return function (error) { 15 | if (error) { 16 | client.increment('err', 1, tags); 17 | } else { 18 | client.increment('ok', 1, tags); 19 | } 20 | client.timing('time', startTime, tags); 21 | return callback.apply(null , arguments); 22 | }; 23 | }; 24 | } 25 | 26 | module.exports = factory; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Morten Siebuhr ", 3 | "name": "statsd-client", 4 | "description": "Yet another client for Etsy's statsd", 5 | "keywords": [ 6 | "statsd", 7 | "client", 8 | "metrics", 9 | "udp", 10 | "tcp" 11 | ], 12 | "version": "0.4.7", 13 | "homepage": "https://github.com/msiebuhr/node-statsd-client", 14 | "bugs": "https://github.com/msiebuhr/node-statsd-client/issues", 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/msiebuhr/node-statsd-client.git" 18 | }, 19 | "main": "lib/statsd-client.js", 20 | "devDependencies": { 21 | "chai": "~1.3.0", 22 | "eslint": "^4.16.0", 23 | "express": "^4.13.4", 24 | "mocha": "~1.6.0", 25 | "offline-github-changelog": "^1.6.1", 26 | "sinon": "^1.17.3", 27 | "supertest": "^1.2.0" 28 | }, 29 | "scripts": { 30 | "test": "mocha -R spec && eslint .", 31 | "preversion": "offline-github-changelog --next=${npm_package_version} > CHANGELOG.md && git add CHANGELOG.md" 32 | }, 33 | "license": "MIT", 34 | "files": [ 35 | "REAMDE.md", 36 | "lib/", 37 | "examples/" 38 | ], 39 | "optionalDependencies": {} 40 | } 41 | -------------------------------------------------------------------------------- /test/messageCollector.js: -------------------------------------------------------------------------------- 1 | /* A small fake UDP-server. 2 | */ 3 | function MessageCollector() { 4 | this._packetsReceived = []; 5 | this._expectedPackets = []; 6 | } 7 | 8 | MessageCollector.prototype.addMessage = function (msg) { 9 | var that = this; 10 | msg.toString().split('\n').forEach(function (part) { 11 | that._packetsReceived.push(part); 12 | }); 13 | this.checkMessages(); 14 | }; 15 | 16 | /* Expect `message` to arrive and call `cb` if/when it does. 17 | */ 18 | MessageCollector.prototype.expectMessage = function (message, cb) { 19 | var that = this; 20 | this._expectedPackets.push({ 21 | message: message, 22 | callback: cb 23 | }); 24 | process.nextTick(function () { 25 | that.checkMessages(); 26 | }); 27 | }; 28 | 29 | /* Check for expected messages. 30 | */ 31 | MessageCollector.prototype.checkMessages = function () { 32 | var that = this; 33 | this._expectedPackets.forEach(function (details, detailIndex) { 34 | // Is it in there? 35 | var i = that._packetsReceived.indexOf(details.message); 36 | if (i !== -1) { 37 | // Remove message and the listener from their respective lists 38 | that._packetsReceived.splice(i, 1); 39 | that._expectedPackets.splice(detailIndex, 1); 40 | return details.callback(); 41 | } 42 | }); 43 | }; 44 | 45 | module.exports = MessageCollector; 46 | -------------------------------------------------------------------------------- /test/FakeServer.js: -------------------------------------------------------------------------------- 1 | /* A small fake UDP-server. 2 | */ 3 | var dgram = require('dgram'); 4 | 5 | function FakeServer(options) { 6 | options = options || {}; 7 | this.port = options.port || 8125; 8 | 9 | this._socket = undefined; 10 | this._packetsReceived = []; 11 | this._expectedPackets = []; 12 | } 13 | 14 | /* Start the server and listen for messages. 15 | */ 16 | FakeServer.prototype.start = function (cb) { 17 | var that = this; 18 | this._socket = dgram.createSocket('udp4'); 19 | 20 | this._socket.on('message', function (msg/*, rinfo*/) { 21 | //console.warn("Server got: '" + msg.toString() + "'"); 22 | msg.toString().split("\n").forEach(function (part) { 23 | that._packetsReceived.push(part); 24 | }); 25 | that.checkMessages(); 26 | }); 27 | 28 | this._socket.on("listening", cb); 29 | 30 | this._socket.bind(this.port); 31 | }; 32 | 33 | /* For closing server down after use. 34 | */ 35 | FakeServer.prototype.stop = function () { 36 | this._socket.close(); 37 | this._socket = undefined; 38 | }; 39 | 40 | /* Expect `message` to arrive and call `cb` if/when it does. 41 | */ 42 | FakeServer.prototype.expectMessage = function (message, cb) { 43 | var that = this; 44 | this._expectedPackets.push({ 45 | message: message, 46 | callback: cb 47 | }); 48 | process.nextTick(function () { 49 | that.checkMessages(); 50 | }); 51 | }; 52 | 53 | /* Check for expected messages. 54 | */ 55 | FakeServer.prototype.checkMessages = function () { 56 | var that = this; 57 | this._expectedPackets.forEach(function (details, detailIndex) { 58 | // Is it in there? 59 | var i = that._packetsReceived.indexOf(details.message); 60 | if (i !== -1) { 61 | // Remove message and the listener from their respective lists 62 | that._packetsReceived.splice(i, 1); 63 | that._expectedPackets.splice(detailIndex, 1); 64 | return details.callback(); 65 | } 66 | }); 67 | }; 68 | 69 | module.exports = FakeServer; 70 | -------------------------------------------------------------------------------- /lib/helpers/getExpressMiddleware.js: -------------------------------------------------------------------------------- 1 | 2 | // Removes ":", heading/trailing / and replaces / by _ in a given route name 3 | function sanitize(routeName) { 4 | return routeName.replace(/:/g, "").replace(/^\/|\/$/g, "").replace(/\//g, "_"); 5 | } 6 | 7 | // Extracts a route name from the request or response 8 | function findRouteName(req, res) { 9 | // Did we get a hardcoded name, or should we figure one out? 10 | if (res.locals && res.locals.statsdUrlKey) { 11 | 12 | return res.locals.statsdUrlKey; 13 | } 14 | 15 | if (req.route && req.route.path) { 16 | var routeName = req.route.path; 17 | 18 | if (Object.prototype.toString.call(routeName) === '[object RegExp]') { 19 | routeName = routeName.source; 20 | } 21 | 22 | if (req.baseUrl) { 23 | routeName = req.baseUrl + routeName; 24 | } else if (routeName === '/') { 25 | routeName = 'root'; 26 | } 27 | 28 | if (req.params) { 29 | Object.keys(req.params).forEach(function(key) { 30 | if (req.params[key] === '') return; 31 | routeName = routeName.replace(req.params[key], ':' + key); 32 | }); 33 | } 34 | 35 | // Appends the HTTP method 36 | return req.method + '_' + sanitize(routeName); 37 | } 38 | } 39 | 40 | /* 41 | * Return express middleware that measures overall performance. 42 | * 43 | * The `prefix` defaults to `''` (but is currently mandatory). The 44 | * `options`-argument is optional. 45 | * * You can set `timeByUrl`, that add a timer per URL template (ex: 46 | * `/api/:username/:thingie`). This can be changed run-time by setting 47 | * `res.locals.statsdUrlKey`. 48 | * * Add a `function(client, startTime, req, res)` in `onResponseEnd` that 49 | * will be called at the very end. 50 | */ 51 | function factory(parentClient) { 52 | return function (prefix, options) { 53 | options = options || {}; 54 | 55 | var client = parentClient.getChildClient(prefix || ''); 56 | var timeByUrl = options.timeByUrl || false; 57 | var notFoundRouteName = options.notFoundRouteName || 'unknown_express_route'; 58 | var onResponseEnd = options.onResponseEnd || undefined; 59 | 60 | return function (req, res, next) { 61 | var startTime = new Date(); 62 | 63 | // Shadow end request 64 | var end = res.end; 65 | res.end = function () { 66 | res.end = end; 67 | end.apply(res, arguments); 68 | 69 | var urlPrefix = ''; 70 | 71 | // Time by URL? 72 | if (timeByUrl) { 73 | urlPrefix += '.'; 74 | urlPrefix += findRouteName(req, res) || notFoundRouteName; 75 | client.increment('response_code' + urlPrefix + '.' + res.statusCode); 76 | } 77 | 78 | client.increment('response_code.' + res.statusCode); 79 | client.timing('response_time' + urlPrefix, startTime); 80 | 81 | if (onResponseEnd) { 82 | onResponseEnd(client, startTime, req, res); 83 | } 84 | }; 85 | next(); 86 | }; 87 | }; 88 | } 89 | 90 | module.exports = factory; 91 | -------------------------------------------------------------------------------- /test/HttpSocket.js: -------------------------------------------------------------------------------- 1 | /* Test the Http socket 2 | */ 3 | 4 | var HttpSocket = require('../lib/HttpSocket'), 5 | http = require('http'), 6 | MessageCollector = require('./messageCollector'), 7 | assert = require('chai').assert; 8 | 9 | /*global describe before it after*/ 10 | 11 | describe('HttpSocket', function () { 12 | var s, e, messages, lastHeaders; 13 | 14 | before(function (done) { 15 | e = new HttpSocket({ host: 'http://localhost:8125' }); 16 | lastHeaders = null; 17 | messages = new MessageCollector(); 18 | s = http.createServer(function (req, res) { 19 | lastHeaders = req.headers; 20 | req.setEncoding('ascii'); 21 | var m = ''; 22 | req.on('data', function (data) { 23 | m += data; 24 | }); 25 | req.on('end', function (data) { 26 | if (data) { m += data; } 27 | messages.addMessage(m); 28 | res.end(); 29 | }); 30 | }); 31 | s.listen(8125, undefined, undefined, done); 32 | }); 33 | 34 | after(function () { 35 | s.close(); 36 | }); 37 | 38 | it("Respects host-configuration", function (done) { 39 | var w = new HttpSocket({host: 'some_other_host.sbhr.dk'}); 40 | w.send('wrong_message'); 41 | 42 | setTimeout(function () { 43 | assert.lengthOf(messages._packetsReceived, 0); 44 | done(); 45 | }, 25); 46 | }); 47 | 48 | it("Sends data immediately with maxBufferSize = 0", function (done) { 49 | var withoutBuffer = new HttpSocket({maxBufferSize: 0, host: 'http://localhost:8125'}), 50 | start = Date.now(); 51 | 52 | withoutBuffer.send('do_not_buffer'); 53 | 54 | messages.expectMessage('do_not_buffer', function () { 55 | assert.closeTo(Date.now() - start, 0, 25); 56 | withoutBuffer.close(); 57 | done(); 58 | }, 500); 59 | }); 60 | 61 | it("Doesn't send data immediately with maxBufferSize > 0", function (done) { 62 | var withBuffer = new HttpSocket({socketTimeout: 25, host: 'http://localhost:8125'}); 63 | withBuffer.send('buffer_this'); 64 | var start = Date.now(); 65 | 66 | messages.expectMessage('buffer_this', function (err) { 67 | assert.operator(Date.now() - start, '>=', 25); 68 | withBuffer.close(); 69 | done(err); 70 | }); 71 | }); 72 | 73 | it("Sends headers", function (done) { 74 | var headers = {'X-Test': 'Test'}; 75 | var withHeaders = new HttpSocket({headers: headers, host: 'http://localhost:8125'}); 76 | withHeaders.send('no heders kthxbai'); 77 | messages.expectMessage('no heders kthxbai', function (err) { 78 | assert.isNotNull(lastHeaders); 79 | assert.equal(lastHeaders['x-test'], 'Test'); 80 | withHeaders.close(); 81 | done(err); 82 | }); 83 | }); 84 | 85 | it("Send 500 messages", function (done) { 86 | this.slow(500); 87 | 88 | // Send messages 89 | for (var i = 0; i < 500; i += 1) { 90 | e.send('foobar' + i); 91 | } 92 | e.close(); 93 | 94 | setTimeout(function () { 95 | // Received some packets 96 | assert.closeTo( 97 | messages._packetsReceived.length, 98 | 500, // Should get 500 99 | 5 // ±5 100 | ); 101 | messages._packetsReceived = []; 102 | return done(); 103 | }, 25); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /lib/HttpSocket.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var urlParse = require('url').parse; 3 | var debug = require('util').debuglog('statsd-client'); 4 | 5 | /*global console*/ 6 | 7 | function HttpSocket(options) { 8 | options = options || {}; 9 | 10 | this._requestOptions = urlParse(options.host || 'http://localhost/'); 11 | this._requestOptions.method = 'PUT'; 12 | this._requestOptions.headers = options.headers || {}; 13 | this._socketTimeoutMsec = 'socketTimeout' in options ? options.socketTimeout : 1000; 14 | 15 | // Require the correct HTTP library 16 | if (this._requestOptions.protocol === 'https:') { 17 | http = require('https'); 18 | } 19 | 20 | this._maxBufferSize = 'maxBufferSize' in options ? options.maxBufferSize : 10000; 21 | 22 | // Set up re-usable socket 23 | this._socketTimer = undefined; // Reference to check-timer 24 | this._buffer = ""; 25 | } 26 | 27 | /* Checks if there is anything in it's buffer that need to be sent. If it is 28 | * non-empty, it will be flushed. 29 | */ 30 | HttpSocket.prototype._socketTimeout = function _socketTimeout() { 31 | debug("_socketTimeout()"); 32 | // Flush the buffer, if it contain anything. 33 | if (this._buffer.length > 0) { 34 | this._flushBuffer(); 35 | return; 36 | } 37 | }; 38 | 39 | 40 | /* 41 | * Flush all current data and stop any timers running. 42 | */ 43 | HttpSocket.prototype.close = function close() { 44 | debug("close()"); 45 | if (this._buffer.length > 0) { 46 | this._flushBuffer(); 47 | } 48 | 49 | // Cancel the running timer 50 | if (this._socketTimer) { 51 | clearInterval(this._socketTimer); 52 | this._socketTimer = undefined; 53 | } 54 | 55 | // Wait a tick or two, so any remaining stats can be sent. 56 | setTimeout(this.kill.bind(this), 10); 57 | }; 58 | 59 | /* Kill the socket RIGHT NOW. 60 | */ 61 | HttpSocket.prototype.kill = function kill() { 62 | debug("kill()"); 63 | 64 | // Clear the timer and catch any further errors silently 65 | if (this._socketTimer) { 66 | clearInterval(this._socketTimer); 67 | this._socketTimer = undefined; 68 | } 69 | }; 70 | 71 | /* Buffer management 72 | */ 73 | HttpSocket.prototype._enqueue = function _enqueue(data) { 74 | debug("_enqueue(", data, ")"); 75 | 76 | if (!this._socketTimer) { 77 | this._socketTimer = setInterval(this._socketTimeout.bind(this), this._socketTimeoutMsec); 78 | } 79 | 80 | // Empty buffer if it's too full 81 | if (this._buffer.length + data.length > this._maxBufferSize) { 82 | this._flushBuffer(); 83 | } 84 | 85 | if (this._buffer.length === 0) { 86 | this._buffer = data; 87 | } else { 88 | this._buffer += "\n" + data; 89 | } 90 | }; 91 | 92 | HttpSocket.prototype._flushBuffer = function _flushBuffer() { 93 | debug("_flushBuffer() →", this._buffer); 94 | this._send(this._buffer); 95 | this._buffer = ""; 96 | }; 97 | 98 | /* Send data - public interface. 99 | */ 100 | HttpSocket.prototype.send = function send(data) { 101 | debug("send(", data, ")"); 102 | if (this._maxBufferSize === 0) { 103 | return this._send(data); 104 | } else { 105 | this._enqueue(data); 106 | } 107 | }; 108 | 109 | /* 110 | * Send data. 111 | */ 112 | HttpSocket.prototype._send = function _send(data) { 113 | debug("_send(", data, ")"); 114 | var req = http.request(this._requestOptions); 115 | 116 | // Catch but ignore errors 117 | req.once('error', function () {}); 118 | 119 | // Send data 120 | req.end(data); 121 | 122 | debug(data); 123 | }; 124 | 125 | module.exports = HttpSocket; 126 | -------------------------------------------------------------------------------- /test/EphemeralSocket.js: -------------------------------------------------------------------------------- 1 | /* Test the Ephemeral socket 2 | */ 3 | 4 | var EphemeralSocket = require('../lib/EphemeralSocket'), 5 | FakeServer = require('./FakeServer'), 6 | assert = require('chai').assert; 7 | 8 | /*global describe before it after*/ 9 | 10 | describe('EphemeralSocket', function () { 11 | var s, e; 12 | 13 | before(function (done) { 14 | s = new FakeServer(); 15 | e = new EphemeralSocket(); 16 | 17 | s.start(done); 18 | }); 19 | 20 | after(function () { 21 | s.stop(); 22 | }); 23 | 24 | it("Respects host-configuration", function (done) { 25 | var w = new EphemeralSocket({host: 'some_other_host.sbhr.dk'}); 26 | w.send('wrong_message'); 27 | 28 | setTimeout(function () { 29 | assert.lengthOf(s._packetsReceived, 0); 30 | done(); 31 | }, 25); 32 | }); 33 | 34 | 35 | it("Convert the port to integer", function () { 36 | var testSocket = new EphemeralSocket({port: "1234"}); 37 | assert.strictEqual(testSocket._port, 1234); 38 | }); 39 | 40 | it("Sends data immediately with maxBufferSize = 0", function (done) { 41 | var withoutBuffer = new EphemeralSocket({maxBufferSize: 0}), 42 | start = Date.now(); 43 | 44 | withoutBuffer.send('do_not_buffer'); 45 | 46 | s.expectMessage('do_not_buffer', function (err) { 47 | assert.closeTo(Date.now() - start, 0, 20); 48 | withoutBuffer.close(); 49 | done(err); 50 | }); 51 | }); 52 | 53 | it("Doesn't send data immediately with maxBufferSize > 0", function (done) { 54 | var withBuffer = new EphemeralSocket({socketTimeout: 25}); 55 | withBuffer.send('buffer_this'); 56 | var start = Date.now(); 57 | 58 | s.expectMessage('buffer_this', function (err) { 59 | assert.operator(Date.now() - start, '>=', 25); 60 | withBuffer.close(); 61 | done(err); 62 | }); 63 | }); 64 | 65 | it("Send 500 messages", function (done) { 66 | this.slow(500); 67 | 68 | // Send messages 69 | for (var i = 0; i < 500; i += 1) { 70 | e.send('foobar' + i); 71 | } 72 | e.close(); 73 | 74 | setTimeout(function () { 75 | // Received some packets 76 | assert.closeTo( 77 | s._packetsReceived.length, 78 | 500, // Should get 500 79 | 5 // ±5 80 | ); 81 | s._packetsReceived = []; 82 | return done(); 83 | }, 25); 84 | }); 85 | 86 | it("Closes _socket when 'error' is emitted", function (done) { 87 | e._createSocket(function () { 88 | // Emit error, wait some and check. 89 | e._socket.emit('error'); 90 | setTimeout(function () { 91 | assert(!e._socket, "Socket isn't closed."); 92 | done(); 93 | }, 10); 94 | }); 95 | }); 96 | 97 | it("Does not crash when many errors are emitted", function (done) { 98 | var s = new EphemeralSocket(); 99 | s._createSocket(function () { 100 | function emitError() { 101 | if (s._socket) { 102 | s._socket.emit('error'); 103 | process.nextTick(emitError); 104 | } 105 | } 106 | emitError(); 107 | 108 | setTimeout(function () { 109 | assert(!s._socket, "Socket isn't closed."); 110 | done(); 111 | }, 5); 112 | }); 113 | }); 114 | 115 | describe("Socket timeout", function () { 116 | var te; 117 | before(function (done) { 118 | te = new EphemeralSocket({ 119 | socketTimeout: 1 120 | }); 121 | te._createSocket(done); 122 | }); 123 | 124 | it("Is open → sleep 10ms → closed", function (done) { 125 | assert(te._socket, "Socket isn't open; should be."); 126 | setTimeout(function () { 127 | assert(!te._socket, "Socket is open; shouldn't be."); 128 | done(); 129 | }, 15); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /lib/EphemeralSocket.js: -------------------------------------------------------------------------------- 1 | var dgram = require('dgram'); 2 | var debug = require('util').debuglog('statsd-client'); 3 | 4 | /*global console*/ 5 | 6 | function EphemeralSocket(options) { 7 | options = options || {}; 8 | 9 | this._hostname = options.host || 'localhost'; 10 | this._port = parseInt(options.port, 10) || 8125; 11 | this._sockettype = options.ipv6 ? 'udp6' : 'udp4'; 12 | this._socketTimeoutMsec = 'socketTimeout' in options ? options.socketTimeout : 1000; 13 | 14 | // Check https://github.com/etsy/statsd/blob/master/docs/metric_types.md#multi-metric-packets for advisable sizes. 15 | this._maxBufferSize = 'maxBufferSize' in options ? options.maxBufferSize : 1200; 16 | 17 | // Set up re-usable socket 18 | this._socket = undefined; // Store the socket here 19 | this._socketUsed = false; // Flag if it has been used 20 | this._socketTimer = undefined; // Reference to check-timer 21 | this._buffer = ""; 22 | } 23 | 24 | EphemeralSocket.prototype.log = function log(/*messages*/) { 25 | //console.log.apply(null, arguments); 26 | }; 27 | 28 | /* Dual-use timer. 29 | * 30 | * First checks if there is anything in it's buffer that need to be sent. If it 31 | * is non-empty, it will be flushed. (And thusly, the socket is in use and we 32 | * stop checking further right away). 33 | * 34 | * If there is nothing in the buffer and the socket hasn't been used in the 35 | * previous interval, close it. 36 | */ 37 | EphemeralSocket.prototype._socketTimeout = function _socketTimeout() { 38 | debug("close()"); 39 | // Flush the buffer, if it contain anything. 40 | if (this._buffer.length > 0) { 41 | this._flushBuffer(); 42 | return; 43 | } 44 | 45 | // Is it already closed? -- then stop here 46 | if (!this._socket) { 47 | return; 48 | } 49 | 50 | // Has been used? -- reset use-flag and wait some more 51 | if (this._socketUsed) { 52 | this._socketUsed = false; 53 | return; 54 | } 55 | 56 | // Not used? -- close the socket 57 | this.close(); 58 | }; 59 | 60 | 61 | /* 62 | * Close the socket, if in use and cancel the interval-check, if running. 63 | */ 64 | EphemeralSocket.prototype.close = function close() { 65 | debug("close()"); 66 | if (!this._socket) { 67 | return; 68 | } 69 | 70 | if (this._buffer.length > 0) { 71 | this._flushBuffer(); 72 | } 73 | 74 | // Cancel the running timer 75 | if (this._socketTimer) { 76 | clearInterval(this._socketTimer); 77 | this._socketTimer = undefined; 78 | } 79 | 80 | // Wait a tick or two, so any remaining stats can be sent. 81 | setTimeout(this.kill.bind(this), 10); 82 | }; 83 | 84 | /* Kill the socket RIGHT NOW. 85 | */ 86 | EphemeralSocket.prototype.kill = function kill() { 87 | debug("kill()"); 88 | if (!this._socket) { 89 | return; 90 | } 91 | 92 | // Clear the timer and catch any further errors silently 93 | if (this._socketTimer) { 94 | clearInterval(this._socketTimer); 95 | this._socketTimer = undefined; 96 | } 97 | this._socket.on('error', function () {}); 98 | 99 | this._socket.close(); 100 | this._socket = undefined; 101 | }; 102 | 103 | EphemeralSocket.prototype._createSocket = function _createSocket(callback) { 104 | debug("_createSocket()"); 105 | if (this._socket) { 106 | return callback(); 107 | } 108 | 109 | this._socket = dgram.createSocket(this._sockettype); 110 | 111 | // Listen on 'error'-events, so they don't bubble up to the main 112 | // application. Try closing the socket for now, forcing it to be re-created 113 | // later. 114 | this._socket.once('error', this.kill.bind(this)); 115 | 116 | // Call on when the socket is ready. 117 | this._socket.once('listening', function () { 118 | return callback(); 119 | }); 120 | this._socket.bind({ port: 0, exclusive: true }, null); 121 | 122 | // Start timer, if we have a positive timeout 123 | if (this._socketTimeoutMsec > 0 && !this._socketTimer) { 124 | this._socketTimer = setInterval(this._socketTimeout.bind(this), this._socketTimeoutMsec); 125 | } 126 | }; 127 | 128 | /* Buffer management 129 | */ 130 | EphemeralSocket.prototype._enqueue = function _enqueue(data) { 131 | debug("_enqueue(", data, ")"); 132 | 133 | if (!this._socketTimer) { 134 | this._socketTimer = setInterval(this._socketTimeout.bind(this), this._socketTimeoutMsec); 135 | } 136 | // Empty buffer if it's too full 137 | if (this._buffer.length + data.length > this._maxBufferSize) { 138 | this._flushBuffer(); 139 | } 140 | 141 | if (this._buffer.length === 0) { 142 | this._buffer = data; 143 | } else { 144 | this._buffer += "\n" + data; 145 | } 146 | }; 147 | 148 | EphemeralSocket.prototype._flushBuffer = function _flushBuffer() { 149 | debug("_flushBuffer() →", this._buffer); 150 | this._send(this._buffer); 151 | this._buffer = ""; 152 | }; 153 | 154 | /* Send data - public interface. 155 | */ 156 | EphemeralSocket.prototype.send = function send(data) { 157 | debug("send(", data, ")"); 158 | if (this._maxBufferSize === 0) { 159 | return this._send(data); 160 | } else { 161 | this._enqueue(data); 162 | } 163 | }; 164 | 165 | /* 166 | * Send data. 167 | */ 168 | EphemeralSocket.prototype._send = function _send(data) { 169 | debug("_send(", data, ")"); 170 | // If we don't have a socket, or we have created one but it isn't 171 | // ready yet, we need to enqueue data to send once the socket is ready. 172 | var that = this; 173 | 174 | this._createSocket(function () { 175 | that._socketUsed = true; 176 | 177 | // Create message 178 | var message = Buffer.from(data); 179 | 180 | debug(message.toString()); 181 | 182 | that._socket.send(message, 0, message.length, that._port, that._hostname); 183 | }); 184 | }; 185 | 186 | module.exports = EphemeralSocket; 187 | -------------------------------------------------------------------------------- /lib/statsd-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Set up the statsd-client. 3 | * 4 | * Requires the `hostname`. Options currently allows for `port` and `debug` to 5 | * be set. 6 | */ 7 | function StatsDClient(options) { 8 | this.options = options || {}; 9 | this._helpers = undefined; 10 | 11 | this.options.tags = this.options.tags || {}; 12 | 13 | // Set defaults 14 | this.options.prefix = this.options.prefix || ""; 15 | 16 | // Prefix? 17 | if (this.options.prefix && this.options.prefix !== "") { 18 | // Add trailing dot if it's missing 19 | var p = this.options.prefix; 20 | this.options.prefix = p[p.length - 1] === '.' ? p : p + "."; 21 | } 22 | 23 | // Figure out which socket to use 24 | if (this.options._socket) { 25 | // Re-use given socket 26 | this._socket = this.options._socket; 27 | } else if(this.options.tcp) { 28 | //User specifically wants a tcp socket 29 | this._socket = new (require('./TCPSocket'))(this.options); 30 | } else if (this.options.host && this.options.host.match(/^http(s?):\/\//i)) { 31 | // Starts with 'http://', then create a HTTP socket 32 | this._socket = new (require('./HttpSocket'))(this.options); 33 | } else { 34 | // Fall back to a UDP ephemeral socket 35 | this._socket = new (require('./EphemeralSocket'))(this.options); 36 | } 37 | } 38 | 39 | /* 40 | * Get a "child" client with a sub-prefix. 41 | */ 42 | StatsDClient.prototype.getChildClient = function getChildClient(extraPrefix) { 43 | return new StatsDClient({ 44 | prefix: this.options.prefix + extraPrefix, 45 | _socket: this._socket, 46 | tags: this.options.tags 47 | }); 48 | }; 49 | 50 | /* 51 | * gauge(name, value, tags) 52 | */ 53 | StatsDClient.prototype.gauge = function gauge(name, value, tags) { 54 | this._socket.send(this.options.prefix + name + ":" + value + "|g" + this.formatTags(tags)); 55 | 56 | return this; 57 | }; 58 | 59 | /* 60 | * gaugeDelta(name, delta, tags) 61 | */ 62 | StatsDClient.prototype.gaugeDelta = function gaugeDelta(name, delta, tags) { 63 | var sign = delta >= 0 ? "+" : "-"; 64 | this._socket.send(this.options.prefix + name + ":" + sign + Math.abs(delta) + "|g" + this.formatTags(tags)); 65 | 66 | return this; 67 | }; 68 | 69 | /* 70 | * set(name, value, tags) 71 | */ 72 | StatsDClient.prototype.set = function set(name, value, tags) { 73 | this._socket.send(this.options.prefix + name + ":" + value + "|s" + this.formatTags(tags)); 74 | 75 | return this; 76 | }; 77 | 78 | /* 79 | * counter(name, delta, tags) 80 | */ 81 | StatsDClient.prototype.counter = function counter(name, delta, tags) { 82 | this._socket.send(this.options.prefix + name + ":" + delta + "|c" + this.formatTags(tags)); 83 | 84 | return this; 85 | }; 86 | 87 | /* 88 | * increment(name, [delta=1], tags) 89 | */ 90 | StatsDClient.prototype.increment = function increment(name, delta, tags) { 91 | this.counter(name, Math.abs(delta === undefined ? 1 : delta), tags); 92 | 93 | return this; 94 | }; 95 | 96 | /* 97 | * decrement(name, [delta=-1], tags) 98 | */ 99 | StatsDClient.prototype.decrement = function decrement(name, delta, tags) { 100 | this.counter(name, -1 * Math.abs(delta === undefined ? 1 : delta), tags); 101 | 102 | return this; 103 | }; 104 | 105 | /* 106 | * timing(name, date-object | ms, tags) 107 | */ 108 | StatsDClient.prototype.timing = function timing(name, time, tags) { 109 | // Date-object or integer? 110 | var t = time instanceof Date ? new Date() - time : time; 111 | 112 | this._socket.send(this.options.prefix + name + ":" + t + "|ms" + this.formatTags(tags)); 113 | 114 | return this; 115 | }; 116 | 117 | /* 118 | * histogram(name, value, tags) 119 | */ 120 | StatsDClient.prototype.histogram = function histogram(name, value, tags) { 121 | this._socket.send(this.options.prefix + name + ":" + value + "|h" + this.formatTags(tags)); 122 | 123 | return this; 124 | }; 125 | 126 | /* 127 | * distribution(name, value, tags) 128 | */ 129 | StatsDClient.prototype.distribution = function distribution(name, value, tags) { 130 | this._socket.send(this.options.prefix + name + ":" + value + "|d" + this.formatTags(tags)); 131 | 132 | return this; 133 | }; 134 | 135 | /* 136 | * formatTags(tags) 137 | */ 138 | StatsDClient.prototype.formatTags = function formatTags(metric_tags) { 139 | var tags = {}; 140 | 141 | // Merge global tags and metric tags. 142 | // Metric tags overwrite global tags for the same key. 143 | var key; 144 | for (key in this.options.tags) { tags[key] = this.options.tags[key]; } 145 | for (key in metric_tags) { tags[key] = metric_tags[key]; } 146 | 147 | if (!tags || Object.keys(tags).length === 0) { 148 | return ''; 149 | } 150 | return '|#' + Object.keys(tags).map(function(key) { 151 | return key + ':' + tags[key]; 152 | }).join(','); 153 | }; 154 | 155 | /* 156 | * Send raw data to the underlying socket. Useful for dealing with custom 157 | * statsd-extensions in a pinch. 158 | */ 159 | StatsDClient.prototype.raw = function raw(rawData) { 160 | this._socket.send(rawData); 161 | 162 | return this; 163 | }; 164 | 165 | /* 166 | * Close the socket, if in use and cancel the interval-check, if running. 167 | */ 168 | StatsDClient.prototype.close = function close() { 169 | this._socket.close(); 170 | 171 | return this; 172 | }; 173 | 174 | /* 175 | * Return an object with available helpers. 176 | */ 177 | StatsDClient.prototype.__defineGetter__('helpers', function () { 178 | if (!(this._helpers)) { 179 | var helpers = {}, 180 | that = this, 181 | files = require('fs').readdirSync(__dirname + '/helpers'); 182 | 183 | files.forEach(function (filename) { 184 | if (/\.js$/.test(filename) && filename !== 'index.js') { 185 | var name = filename.replace(/\.js$/, ''); 186 | helpers[name] = require('./helpers/' + filename)(that); 187 | } 188 | }); 189 | this._helpers = helpers; 190 | } 191 | 192 | return this._helpers; 193 | }); 194 | 195 | module.exports = StatsDClient; 196 | -------------------------------------------------------------------------------- /lib/TCPSocket.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | var debug = require('util').debuglog('statsd-client'); 3 | 4 | /*global console*/ 5 | 6 | function TCPSocket(options) { 7 | options = options || {}; 8 | 9 | this._hostname = options.host || 'localhost'; 10 | this._port = options.port || 8125; 11 | this._flushBufferTimeout = 'socketTimeout' in options ? options.socketTimeout : 1000; 12 | this._timeoutsToClose = 'socketTimeoutsToClose' in options ? options.socketTimeoutsToClose : 10; // close socket if not used in 10 flush intervals 13 | 14 | // Check https://github.com/etsy/statsd/#multi-metric-packets for advisable sizes. 15 | this._maxBufferSize = 'maxBufferSize' in options ? options.maxBufferSize : 1200; 16 | 17 | // Set up re-usable socket 18 | this._socket = undefined; // Store the socket here 19 | this._socketUsed = false; // Flag if it has been used 20 | this._socketLastUsed = 0; // How many intervals of timeout since socket has been used 21 | this._socketTimer = undefined; // Reference to check-timer 22 | this._buffer = []; 23 | } 24 | 25 | /* Dual-use timer. 26 | * 27 | * First checks if there is anything in it's buffer that need to be sent. If it 28 | * is non-empty, it will be flushed. (And thusly, the socket is in use and we 29 | * stop checking further right away). 30 | * 31 | * If there is nothing in the buffer and the socket hasn't been used in the 32 | * previous interval, close it. 33 | */ 34 | TCPSocket.prototype._socketTimeout = function _socketTimeout() { 35 | debug("close()"); 36 | // Flush the buffer, if it contain anything. 37 | if (this._buffer.length > 0) { 38 | this._flushBuffer(); 39 | return; 40 | } 41 | 42 | // Is it already closed? -- then stop here 43 | if (!this._socket) { 44 | return; 45 | } 46 | 47 | // Not used? 48 | if (this._socketUsed === false) { 49 | this._socketLastUsed++; 50 | // if not used in many intervals, close it 51 | if (this._socketLastUsed >= this._timeoutsToClose) { 52 | this.close(); 53 | return; 54 | } 55 | } else { 56 | this._socketLastUsed = 0; 57 | } 58 | 59 | // Reset whether its been used 60 | this._socketUsed = false; 61 | // Start timer, if we have a positive timeout 62 | if (this._flushBufferTimeout > 0 && !this._socketTimer) { 63 | this._socketTimer = setInterval(this._socketTimeout.bind(this), this._flushBufferTimeout); 64 | } 65 | }; 66 | 67 | 68 | /* 69 | * Close the socket, if in use and cancel the interval-check, if running. 70 | */ 71 | TCPSocket.prototype.close = function close() { 72 | debug("close()"); 73 | if (!this._socket) { 74 | return; 75 | } 76 | 77 | if (this._buffer.length > 0) { 78 | this._flushBuffer(); 79 | } 80 | 81 | // Cancel the running timer 82 | if (this._socketTimer) { 83 | clearInterval(this._socketTimer); 84 | this._socketTimer = undefined; 85 | } 86 | 87 | // Wait a tick or two, so any remaining stats can be sent. 88 | setTimeout(this.kill.bind(this), 10); 89 | }; 90 | 91 | /* Kill the socket RIGHT NOW. 92 | */ 93 | TCPSocket.prototype.kill = function kill() { 94 | debug("kill()"); 95 | if (!this._socket) { 96 | return; 97 | } 98 | 99 | // Clear the timer and catch any further errors silently 100 | if (this._socketTimer) { 101 | clearInterval(this._socketTimer); 102 | this._socketTimer = undefined; 103 | } 104 | this._socket.on('error', function () {}); 105 | 106 | this._socket.end(); 107 | this._socket = undefined; 108 | }; 109 | 110 | TCPSocket.prototype._createSocket = function _createSocket(callback) { 111 | debug("_createSocket()"); 112 | if (this._socket) { 113 | return callback(); 114 | } 115 | 116 | this._socket = net.Socket({ 117 | type: 'tcp4' 118 | }); 119 | 120 | 121 | // Listen on 'error'-events, so they don't bubble up to the main 122 | // application. Try closing the socket for now, forcing it to be re-created 123 | // later. 124 | this._socket.once('error', this.kill.bind(this)); 125 | 126 | // Call on when the socket is ready. 127 | this._socket.connect(this._port, this._hostname, function() { 128 | return callback(); 129 | }); 130 | 131 | // Start timer, if we have a positive timeout 132 | if (this._flushBufferTimeout > 0 && !this._socketTimer) { 133 | this._socketTimer = setInterval(this._socketTimeout.bind(this), this._flushBufferTimeout); 134 | } 135 | }; 136 | 137 | /* Buffer management 138 | */ 139 | TCPSocket.prototype._enqueue = function _enqueue(data) { 140 | debug("_enqueue(", data, ")"); 141 | 142 | if (!this._socketTimer) { 143 | this._socketTimer = setInterval(this._socketTimeout.bind(this), this._flushBufferTimeout); 144 | } 145 | // Empty buffer if it's too full 146 | if (this._buffer.reduce(function(sum, line) { return sum + line.length; }, 0) > this._maxBufferSize) { 147 | this._flushBuffer(); 148 | } 149 | 150 | this._buffer.push(data); 151 | }; 152 | 153 | TCPSocket.prototype._flushBuffer = function _flushBuffer() { 154 | debug("_flushBuffer() →", this._buffer); 155 | var that = this; 156 | this._send(this._buffer, function() { 157 | that._buffer = []; 158 | }); 159 | }; 160 | 161 | /* Send data - public interface. 162 | */ 163 | TCPSocket.prototype.send = function send(data) { 164 | debug("send(", data, ")"); 165 | if (this._maxBufferSize === 0) { 166 | return this._send([data], function() {}); 167 | } else { 168 | this._enqueue(data); 169 | } 170 | }; 171 | 172 | /* 173 | * Send data. 174 | */ 175 | TCPSocket.prototype._send = function _send(data, callback) { 176 | debug("_send(", data, ")"); 177 | // If we don't have a socket, or we have created one but it isn't 178 | // ready yet, we need to enqueue data to send once the socket is ready. 179 | var that = this; 180 | 181 | //Must be asynchronous 182 | this._createSocket(that._writeToSocket.bind(that, data, callback)); 183 | }; 184 | 185 | TCPSocket.prototype._writeToSocket = function _writeToSocket(data, callback) { 186 | // Create message 187 | // Trailing \n important because socket.write will sometimes concat multiple 'write' calls. 188 | var message = new Buffer(data.join('\n') + '\n'); 189 | 190 | debug(message.toString()); 191 | if (this._socket) { 192 | this._socketUsed = true; 193 | this._socket.write(message); 194 | } 195 | 196 | callback(); 197 | }; 198 | 199 | module.exports = TCPSocket; 200 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var StatsDClient = require('../lib/statsd-client'), 2 | FakeServer = require('./FakeServer'), 3 | assert = require('chai').assert, 4 | express = require('express'), 5 | supertest = require('supertest'), 6 | sinon = require('sinon'); 7 | 8 | /*global describe before it*/ 9 | 10 | describe('Helpers', function () { 11 | var c; 12 | var s; 13 | var es; 14 | var baseUrl; 15 | 16 | before(function (done) { 17 | s = new FakeServer(); 18 | c = new StatsDClient({ 19 | maxBufferSize: 0 20 | }); 21 | 22 | var app = express(); 23 | 24 | app.use(c.helpers.getExpressMiddleware('express', { timeByUrl: true })); 25 | 26 | // Routes defined on the express app itself. 27 | app.get('/', function (req, res) { 28 | res.sendStatus(200); 29 | }); 30 | 31 | app.get('/foo', function (req, res) { 32 | res.sendStatus(200); 33 | }); 34 | 35 | app.post('/foo', function (req, res) { 36 | res.sendStatus(200); 37 | }); 38 | 39 | app.get('/foo/:param/bar', function (req, res) { 40 | res.sendStatus(200); 41 | }); 42 | 43 | // Routes defined on the a subrouter or "micro-app". 44 | var router = express.Router({ mergeParams: true }); 45 | 46 | router.get('/foo', function (req, res) { 47 | res.sendStatus(200); 48 | }); 49 | 50 | router.get('/foo/:subparam', function (req, res) { 51 | res.sendStatus(200); 52 | }); 53 | 54 | app.use('/subrouter/:param', router); 55 | 56 | es = app.listen(3000, function () { 57 | baseUrl = 'http://localhost:' + 3000; 58 | s.start(done); 59 | }); 60 | }); 61 | 62 | after(function (done) { 63 | es.on('close', function () { 64 | s.stop(); 65 | done(); 66 | }); 67 | es.close(); 68 | }); 69 | 70 | it('.helpers is an object', function () { 71 | assert.isObject(c.helpers); 72 | }); 73 | 74 | it('.getExpressMiddleware(prefix) → function (err, res, next)', function () { 75 | var f = c.helpers.getExpressMiddleware('prefix'); 76 | assert.isFunction(f); 77 | assert.lengthOf(f, 3); 78 | }); 79 | 80 | describe('response times', function () { 81 | var sandbox; 82 | beforeEach(function () { 83 | sandbox = sinon.sandbox.create(); 84 | sandbox.useFakeTimers(new Date().valueOf(), 'Date'); 85 | }); 86 | 87 | afterEach(function () { 88 | sandbox.restore(); 89 | }); 90 | 91 | describe('GET', function () { 92 | it('should count the response code', function (done) { 93 | supertest(baseUrl) 94 | .get('/') 95 | .expect(200) 96 | .end(function (err/*, res*/) { 97 | if (err) return done(err); 98 | s.expectMessage('express.response_code.200:1|c', done); 99 | }); 100 | }); 101 | 102 | it('should count the response code with the url prefix', function (done) { 103 | supertest(baseUrl) 104 | .get('/') 105 | .expect(200) 106 | .end(function (err/*, res*/) { 107 | if (err) return done(err); 108 | s.expectMessage('express.response_code.GET_root.200:1|c', done); 109 | }); 110 | }); 111 | 112 | it('/ → "GET_root"', function (done) { 113 | supertest(baseUrl) 114 | .get('/') 115 | .expect(200) 116 | .end(function (err/*, res*/) { 117 | if (err) return done(err); 118 | s.expectMessage('express.response_time.GET_root:0|ms', done); 119 | }); 120 | }); 121 | 122 | it('/foo → "GET_foo"', function (done) { 123 | supertest(baseUrl) 124 | .get('/foo') 125 | .expect(200) 126 | .end(function (err/*, res*/) { 127 | if (err) return done(err); 128 | s.expectMessage('express.response_time.GET_foo:0|ms', done); 129 | }); 130 | }); 131 | 132 | it('/foo/:param/bar → "GET_foo_param_bar"', function (done) { 133 | supertest(baseUrl) 134 | .get('/foo/mydynamicparameter/bar') 135 | .expect(200) 136 | .end(function (err/*, res*/) { 137 | if (err) return done(err); 138 | s.expectMessage('express.response_time.GET_foo_param_bar:0|ms', done); 139 | }); 140 | }); 141 | 142 | describe('sub-router', function () { 143 | it('/subrouter/:param/foo → "GET_subrouter_param_foo"', function (done) { 144 | supertest(baseUrl) 145 | .get('/subrouter/test/foo') 146 | .expect(200) 147 | .end(function (err/*, res*/) { 148 | if (err) return done(err); 149 | s.expectMessage('express.response_time.GET_subrouter_param_foo:0|ms', done); 150 | }); 151 | }); 152 | 153 | it('/subrouter/:param/foo/:subparam → "GET_subrouter_param_foo_subparam"', function (done) { 154 | supertest(baseUrl) 155 | .get('/subrouter/test_param/foo/test_sub_param') 156 | .expect(200) 157 | .end(function (err/*, res*/) { 158 | if (err) return done(err); 159 | s.expectMessage('express.response_time.GET_subrouter_param_foo_subparam:0|ms', done); 160 | }); 161 | }); 162 | }); 163 | }); 164 | 165 | describe('POST', function () { 166 | it('/foo → "POST_foo"', function (done) { 167 | supertest(baseUrl) 168 | .post('/foo') 169 | .expect(200) 170 | .end(function (err/*, res*/) { 171 | if (err) return done(err); 172 | s.expectMessage('express.response_time.POST_foo:0|ms', done); 173 | }); 174 | }); 175 | }); 176 | }); 177 | 178 | it('.wrapCallback(prefix, callback, options) → function', function () { 179 | var callback = function () {}; 180 | var f = c.helpers.wrapCallback('prefix', callback); 181 | assert.isFunction(f); 182 | }); 183 | 184 | describe('wrapped callback', function () { 185 | var sandbox; 186 | beforeEach(function () { 187 | sandbox = sinon.sandbox.create(); 188 | sandbox.useFakeTimers(new Date().valueOf(), 'Date'); 189 | }); 190 | 191 | afterEach(function () { 192 | sandbox.restore(); 193 | }); 194 | 195 | it('invokes original callback', function (done) { 196 | var e = new Error('test'), v = 20; 197 | var callback = function (arg1, arg2) { 198 | assert.strictEqual(arg1, e); 199 | assert.strictEqual(arg2, v); 200 | return done(); 201 | }; 202 | var f = c.helpers.wrapCallback('prefix', callback); 203 | f(e, v); 204 | }); 205 | 206 | describe('sends metrics', function () { 207 | var callback = function () {}; 208 | var f; 209 | before(function () { 210 | f = c.helpers.wrapCallback('callback', callback, { 211 | tags: { foo: 'bar' } 212 | }); 213 | f(); 214 | }); 215 | 216 | // It seem to have some timing-issue where a millisecond actually manages to pass... 217 | it.skip('sends timing', function (done) { 218 | s.expectMessage('callback.time:0|ms|#foo:bar', done); 219 | }); 220 | 221 | it('counts errors', function (done) { 222 | f(new Error('test')); 223 | s.expectMessage('callback.err:1|c|#foo:bar', done); 224 | }); 225 | 226 | it('counts successes', function (done) { 227 | s.expectMessage('callback.ok:1|c|#foo:bar', done); 228 | }); 229 | }); 230 | }); 231 | }); 232 | -------------------------------------------------------------------------------- /test/StatsDClient.js: -------------------------------------------------------------------------------- 1 | var StatsDClient = require('../lib/statsd-client'), 2 | HttpSocket = require('../lib/HttpSocket'), 3 | EphemeralSocket = require('../lib/EphemeralSocket'), 4 | FakeServer = require('./FakeServer'), 5 | assert = require('chai').assert; 6 | 7 | /*global describe before it after*/ 8 | 9 | describe('StatsDClient', function () { 10 | describe('Namespaces', function () { 11 | it('test → test.', function () { 12 | assert.equal(new StatsDClient({prefix: 'test'}).options.prefix, 'test.'); 13 | }); 14 | 15 | it('test. → test.', function () { 16 | assert.equal(new StatsDClient({prefix: 'test.'}).options.prefix, 'test.'); 17 | }); 18 | }); 19 | 20 | var s, c; 21 | 22 | before(function (done) { 23 | s = new FakeServer(); 24 | c = new StatsDClient({ 25 | maxBufferSize: 0 26 | }); 27 | s.start(done); 28 | }); 29 | 30 | after(function () { 31 | s.stop(); 32 | }); 33 | 34 | describe("Counters", function () { 35 | it('.counter("abc", 1) → "abc:1|c', function (done) { 36 | c.counter('abc', 1); 37 | s.expectMessage('abc:1|c', done); 38 | }); 39 | 40 | it('.counter("abc", -5) → "abc:-5|c', function (done) { 41 | c.counter('abc', -5); 42 | s.expectMessage('abc:-5|c', done); 43 | }); 44 | 45 | it('.increment("abc") → "abc:1|c', function (done) { 46 | c.increment('abc'); 47 | s.expectMessage('abc:1|c', done); 48 | }); 49 | 50 | it('.increment("abc", 10) → "abc:10|c', function (done) { 51 | c.increment('abc', 10); 52 | s.expectMessage('abc:10|c', done); 53 | }); 54 | 55 | it('.decrement("abc", -2) → "abc:-2|c', function (done) { 56 | c.decrement('abc', -2); 57 | s.expectMessage('abc:-2|c', done); 58 | }); 59 | 60 | it('.decrement("abc", 3) → "abc:-3|c', function (done) { 61 | c.decrement('abc', -3); 62 | s.expectMessage('abc:-3|c', done); 63 | }); 64 | 65 | it('.counter("abc", 0) → "abc:0|c', function (done) { 66 | c.counter('abc', 0); 67 | s.expectMessage('abc:0|c', done); 68 | }); 69 | 70 | it('.decrement("abc", 0) → "abc:0|c', function (done) { 71 | c.decrement('abc', 0); 72 | s.expectMessage('abc:0|c', done); 73 | }); 74 | }); 75 | 76 | describe('Gauges', function () { 77 | it('.gauge("gauge", 3) → "gauge:3|g', function (done) { 78 | c.gauge('gauge', 3); 79 | s.expectMessage('gauge:3|g', done); 80 | }); 81 | 82 | it('.gaugeDelta("gauge", 3) → "gauge:+3|g', function (done) { 83 | c.gaugeDelta('gauge', 3); 84 | s.expectMessage('gauge:+3|g', done); 85 | }); 86 | 87 | it('.gaugeDelta("gauge", -3) → "gauge:-3|g', function (done) { 88 | c.gaugeDelta('gauge', -3); 89 | s.expectMessage('gauge:-3|g', done); 90 | }); 91 | }); 92 | 93 | describe('Sets', function () { 94 | it('.set("foo", 10) → "foo:10|s', function (done) { 95 | c.set('foo', 10); 96 | s.expectMessage('foo:10|s', done); 97 | }); 98 | }); 99 | 100 | describe('Histograms', function () { 101 | it('.histogram("foo", 10) → "foo:10|h', function (done) { 102 | c.histogram('foo', 10); 103 | s.expectMessage('foo:10|h', done); 104 | }); 105 | }); 106 | 107 | describe('Distributions', function () { 108 | it('.distribution("foo", 10) → "foo:10|d', function (done) { 109 | c.distribution('foo', 10); 110 | s.expectMessage('foo:10|d', done); 111 | }); 112 | }); 113 | 114 | describe('Timers', function () { 115 | it('.timing("foo", 10) → "foo:10|ms', function (done) { 116 | c.timing('foo', 10); 117 | s.expectMessage('foo:10|ms', done); 118 | }); 119 | 120 | it('.timing("foo", new Date(-20ms)) ~→ "foo:20|ms"', function (done) { 121 | this.slow(100); 122 | var d = new Date(); 123 | setTimeout(function () { 124 | c.timing('foo', d); 125 | 126 | setTimeout(function () { 127 | // Figure out if we git a right-looking message 128 | var sentMessages = s._packetsReceived; 129 | assert.lengthOf(sentMessages, 1); 130 | assert.match( 131 | sentMessages[0], 132 | /foo:[12]\d\|ms/ 133 | ); 134 | 135 | // Expect it anyway, as we need to clean up the packet list. 136 | s.expectMessage(sentMessages[0], done); 137 | }, 10); 138 | }, 20); 139 | }); 140 | }); 141 | 142 | describe('Tags', function () { 143 | it('.histogram("foo", 10) with global tags {"test":"tag","other":"tag"} → "foo:10|h|#test:tag,other:tag"', function (done) { 144 | new StatsDClient({ 145 | maxBufferSize: 0, 146 | tags: { 147 | test: 'tag', 148 | other: 'tag' 149 | } 150 | }).histogram('foo', 10); 151 | s.expectMessage('foo:10|h|#test:tag,other:tag', done); 152 | }); 153 | 154 | it('.histogram("foo", 10) with metric tags {"test":"tag","other":"tag"} → "foo:10|h|#test:tag,other:tag"', function (done) { 155 | c.histogram('foo', 10, { test: 'tag', other: 'tag'}); 156 | s.expectMessage('foo:10|h|#test:tag,other:tag', done); 157 | }); 158 | 159 | it('.distribution("foo", 10) with global tags {"test":"tag","other":"tag"} → "foo:10|d|#test:tag,other:tag"', function (done) { 160 | new StatsDClient({ 161 | maxBufferSize: 0, 162 | tags: { 163 | test: 'tag', 164 | other: 'tag' 165 | } 166 | }).distribution('foo', 10); 167 | s.expectMessage('foo:10|d|#test:tag,other:tag', done); 168 | }); 169 | 170 | it('.distribution("foo", 10) with metric tags {"test":"tag","other":"tag"} → "foo:10|d|#test:tag,other:tag"', function (done) { 171 | c.distribution('foo', 10, { test: 'tag', other: 'tag'}); 172 | s.expectMessage('foo:10|d|#test:tag,other:tag', done); 173 | }); 174 | 175 | describe('metrics tags overwrite global tags', function () { 176 | it('.gauge("foo", 10, {tags}) with global tags → "foo:10|g|#global:tag,other:metric,metric:tag"', function (done) { 177 | new StatsDClient({ 178 | maxBufferSize: 0, 179 | tags: { 180 | global: 'tag', 181 | other: 'tag' 182 | } 183 | }).gauge('foo', 10, {other: 'metric', metric: 'tag'}); 184 | s.expectMessage('foo:10|g|#global:tag,other:metric,metric:tag', done); 185 | }); 186 | }); 187 | }); 188 | 189 | describe('Raw', function () { 190 | it('.raw("foo.bar#123") → "foo.bar#123"', function (done) { 191 | c.raw('foo.bar#123'); 192 | s.expectMessage('foo.bar#123', done); 193 | }); 194 | }); 195 | 196 | describe('Chaining', function() { 197 | it('.gauge() chains', function() { 198 | assert.equal(c, c.gauge('x', 'y')); 199 | }); 200 | 201 | it('.gaugeDelta() chains', function() { 202 | assert.equal(c, c.gaugeDelta('x', 'y')); 203 | }); 204 | 205 | it('.set() chains', function() { 206 | assert.equal(c, c.set('x', 'y')); 207 | }); 208 | 209 | it('.counter() chains', function() { 210 | assert.equal(c, c.counter('x', 'y')); 211 | }); 212 | 213 | it('.increment() chains', function() { 214 | assert.equal(c, c.increment('x', 'y')); 215 | }); 216 | 217 | it('.decrement() chains', function() { 218 | assert.equal(c, c.decrement('x', 'y')); 219 | }); 220 | 221 | it('.timing() chains', function() { 222 | assert.equal(c, c.timing('x', 'y')); 223 | }); 224 | 225 | it('.histogram() chains', function() { 226 | assert.equal(c, c.histogram('x', 'y')); 227 | }); 228 | 229 | it('.distribution() chains', function() { 230 | assert.equal(c, c.distribution('x', 'y')); 231 | }); 232 | 233 | it('.raw() chains', function () { 234 | assert.equal(c, c.raw('foo')); 235 | }); 236 | 237 | it('.close() chains', function() { 238 | assert.equal(c, c.close('x', 'y')); 239 | }); 240 | }); 241 | }); 242 | 243 | describe('StatsDClient / HTTP', function () { 244 | it('Giving plain host=example.com gives UDP backend', function () { 245 | assert.instanceOf( 246 | (new StatsDClient({ host: 'example.com' }))._socket, 247 | EphemeralSocket 248 | ); 249 | }); 250 | 251 | it('Giving host=https://example.com gives HTTP backend', function () { 252 | var c = new StatsDClient({ 253 | host: 'https://example.com' 254 | }); 255 | 256 | assert.instanceOf(c._socket, HttpSocket); 257 | }); 258 | 259 | it('Giving host=http://example.com gives HTTP backend', function () { 260 | assert.instanceOf( 261 | (new StatsDClient({ host: 'http://example.com' }))._socket, 262 | HttpSocket 263 | ); 264 | }); 265 | }); 266 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPRECATED node-statsd-client 2 | ============================= 3 | 4 | > This module is no longer maintained - please use alternative libraries, such as [hot-shots](https://www.npmjs.com/package/hot-shots). 5 | 6 | Node.js client for [statsd](https://github.com/etsy/statsd). 7 | 8 | [![Build Status](https://secure.travis-ci.org/msiebuhr/node-statsd-client.png?branch=master)](http://travis-ci.org/msiebuhr/node-statsd-client) 9 | 10 | Quick tour 11 | ---------- 12 | 13 | ```javascript 14 | var SDC = require('statsd-client'), 15 | sdc = new SDC({host: 'statsd.example.com'}); 16 | 17 | var timer = new Date(); 18 | sdc.increment('some.counter'); // Increment by one. 19 | sdc.gauge('some.gauge', 10); // Set gauge to 10 20 | sdc.timing('some.timer', timer); // Calculates time diff 21 | sdc.histogram('some.histogram', 10, {foo: 'bar'}) // Histogram with tags 22 | sdc.distribution('some.distribution', 10, {foo: 'bar'}) // Distribution with tags 23 | 24 | sdc.close(); // Optional - stop NOW 25 | ``` 26 | 27 | API 28 | --- 29 | 30 | ### Initialization 31 | 32 | ```javascript 33 | var SDC = require('statsd-client'), 34 | sdc = new SDC({host: 'statsd.example.com', port: 8124}); 35 | ``` 36 | 37 | Global options: 38 | * `prefix`: Prefix all stats with this value (default `""`). 39 | * `tcp`: User specifically wants to use tcp (default `false`). 40 | * `socketTimeout`: Dual-use timer. Will flush metrics every interval. For UDP, 41 | it auto-closes the socket after this long without activity (default 1000 ms; 42 | 0 disables this). For TCP, it auto-closes the socket after `socketTimeoutsToClose` number of timeouts have elapsed without activity. 43 | * `tags`: Object of string key/value pairs which will be appended on to all StatsD payloads (excluding raw payloads) (default `{}`) 44 | 45 | UDP options: 46 | * `host`: Where to send the stats (default `localhost`). 47 | * `port`: Port to contact the statsd-daemon on (default `8125`). 48 | * `ipv6`: Use IPv6 instead of IPv4 (default `false`). 49 | 50 | TCP options: 51 | * `host`: Where to send the stats (default `localhost`). 52 | * `port`: Port to contact the statsd-daemon on (default `8125`). 53 | * `socketTimeoutsToClose`: Number of timeouts in which the socket auto-closes if it has been inactive. (default `10`; `1` to auto-close after a single timeout). 54 | 55 | HTTP options: 56 | * `host`: The URL to send metrics to (default: `http://localhost`). 57 | * `headers`: Additional headers to send (default `{}`) 58 | * `method`: What HTTP method to use (default `PUT`) 59 | 60 | To debug, set the environment variable `NODE_DEBUG=statsd-client` when running your program. 61 | 62 | ### Counting stuff 63 | 64 | Counters are supported, both as raw `.counter(metric, delta)` and with the 65 | shortcuts `.increment(metric, [delta=1])` and `.decrement(metric, [delta=-1])`: 66 | 67 | ```javascript 68 | sdc.increment('systemname.subsystem.value'); // Increment by one 69 | sdc.decrement('systemname.subsystem.value', -10); // Decrement by 10 70 | sdc.counter('systemname.subsystem.value', 100); // Increment by 100 71 | ``` 72 | 73 | ### Gauges 74 | 75 | Sends an arbitrary number to the back-end: 76 | 77 | ```javascript 78 | sdc.gauge('what.you.gauge', 100); 79 | sdc.gaugeDelta('what.you.gauge', 20); // Will now count 120 80 | sdc.gaugeDelta('what.you.gauge', -70); // Will now count 50 81 | sdc.gauge('what.you.gauge', 10); // Will now count 10 82 | ``` 83 | 84 | ### Sets 85 | 86 | Send unique occurences of events between flushes to the back-end: 87 | 88 | ```javascript 89 | sdc.set('your.set', 200); 90 | ``` 91 | 92 | ### Delays 93 | 94 | Keep track of how fast (or slow) your stuff is: 95 | 96 | ```javascript 97 | var start = new Date(); 98 | setTimeout(function () { 99 | sdc.timing('random.timeout', start); 100 | }, 100 * Math.random()); 101 | ``` 102 | 103 | If it is given a `Date`, it will calculate the difference, and anything else 104 | will be passed straight through. 105 | 106 | And don't let the name (or nifty interface) fool you - it can measure any kind 107 | of number, where you want to see the distribution (content lengths, list items, 108 | query sizes, ...) 109 | 110 | ### Histogram 111 | 112 | Many implementations (though not the official one from Etsy) support 113 | histograms as an alias/alternative for timers. So aside from the fancy bits 114 | with handling dates, this is much the same as `.timing()`. 115 | 116 | ### Distribution 117 | 118 | Datadog's specific implementation supports another alternative to timers/histograms, 119 | called the [distribution metric type](https://docs.datadoghq.com/metrics/distributions/). 120 | From the client's perspective, this is pretty much an alias to histograms and can be used via `.distribution()`. 121 | 122 | ### Raw 123 | 124 | Passes a raw string to the underlying socket. Useful for dealing with custom 125 | statsd-extensions in a pinch. 126 | 127 | ```javascript 128 | sdc.raw('foo.bar:123|t|@0.5|#key:value'); 129 | ``` 130 | 131 | ### Tags 132 | 133 | All the methods above support metric level tags as their last argument. Just like global tags, the format for metric tags is an object of string key/value pairs. 134 | Tags at the metric level overwrite global tags with the same key. 135 | 136 | ```javascript 137 | sdc.gauge('gauge.with.tags', 100, {foo: 'bar'}); 138 | ``` 139 | 140 | ### Express helper 141 | 142 | There's also a helper for measuring stuff in [Express.js](http://expressjs.com) 143 | via middleware: 144 | 145 | ```javascript 146 | var SDC = require('statsd-client'); 147 | var app = express(); 148 | sdc = new SDC({...}); 149 | 150 | app.use(sdc.helpers.getExpressMiddleware('somePrefix')); 151 | // or 152 | app.get('/', 153 | sdc.helpers.getExpressMiddleware('otherPrefix'), 154 | function (req, res, next) { req.pipe(res); }); 155 | 156 | app.listen(3000); 157 | ``` 158 | 159 | This will count responses by status-code (`prefix.`) and the 160 | overall response-times. 161 | 162 | It can also measure per-URL (e.g. PUT to `/:user/:thing` will become 163 | `PUT_user_thing` by setting the `timeByUrl: true` in the `options`-object: 164 | 165 | ```javascript 166 | app.use(sdc.helpers.getExpressMiddleware('prefix', { timeByUrl: true })); 167 | ``` 168 | 169 | As the names can become rather odd in corner-cases (esp. regexes and non-REST 170 | interfaces), you can specify another value by setting `res.locals.statsdUrlKey` 171 | at a later point. 172 | 173 | The `/` page will appear as `root` (e.g. `GET_root`) in metrics while any not found route will appear as `{METHOD}_unknown_express_route`. You can change that name by setting the `notFoundRouteName` in the middleware options. 174 | 175 | 176 | ### Callback helper 177 | 178 | There's also a helper for measuring stuff with regards to a callback: 179 | 180 | ```javascript 181 | var SDC = requrire('statsd-client'); 182 | sdc = new SDC({...}); 183 | 184 | function doSomethingAsync(arg, callback) { 185 | callback = sdc.helpers.wrapCallback('somePrefix', callback); 186 | // ... do something ... 187 | return callback(null); 188 | } 189 | ``` 190 | 191 | The callback is overwritten with a shimmed version that counts the 192 | number of errors (`prefix.err`) and successes (`prefix.ok`) and 193 | the time of execution of the function (`prefix.time`). 194 | You invoked the shimmed callback exactly the same way as though 195 | there was no shim at all. Yes, you get metrics for your function in 196 | a single line of code. 197 | 198 | Note that the start of execution time is marked as soon as you 199 | invoke `sdc.helpers.wrapCallback()`. 200 | 201 | You can also provide more options: 202 | 203 | ```javascript 204 | sdc.helpers.wrapCallback('somePrefix', callback, { 205 | tags: { foo: 'bar' } 206 | }); 207 | ``` 208 | 209 | ### Stopping gracefully 210 | 211 | By default, the socket is closed if it hasn't been used for a second (see 212 | `socketTimeout` in the init-options), but it can also be force-closed with 213 | `.close()`: 214 | 215 | ```javascript 216 | var start = new Date(); 217 | setTimeout(function () { 218 | sdc.timing('random.timeout', start); // 2 - implicitly re-creates socket. 219 | sdc.close(); // 3 - Closes socket after last use. 220 | }, 100 * Math.random()); 221 | sdc.close(); // 1 - Closes socket early. 222 | ``` 223 | 224 | The call is idempotent, so you can call it "just to be sure". And if you submit 225 | new metrics later, the socket will automatically be re-created, and a new 226 | timeout-timer started. 227 | 228 | ### Prefix-magic 229 | 230 | The library supports getting "child" clients with extra prefixes, to help with 231 | making sane name-spacing in apps: 232 | 233 | ```javascript 234 | // Create generic client 235 | var sdc = new StatsDClient({host: 'statsd.example.com', prefix: 'systemname'}); 236 | sdc.increment('foo'); // Increments 'systemname.foo' 237 | ... do great stuff ... 238 | 239 | // Subsystem A 240 | var sdcA = sdc.getChildClient('a'); 241 | sdcA.increment('foo'); // Increments 'systemname.a.foo' 242 | 243 | // Subsystem B 244 | var sdcB = sdc.getChildClient('b'); 245 | sdcB.increment('foo'); // Increments 'systemname.b.foo' 246 | ``` 247 | 248 | Internally, they all use the same socket, so calling `.close()` on any of them 249 | will allow the entire program to stop gracefully. 250 | 251 | What's broken 252 | ------------- 253 | 254 | Check the [GitHub issues](https://github.com/msiebuhr/node-statsd-client/issues). 255 | 256 | Other resources 257 | --------------- 258 | 259 | * [statsd-tail](https://github.com/msiebuhr/statsd-tail) - A simple program to grab statsd-data on localhost 260 | * [hot-shots](https://www.npmjs.com/package/hot-shots) - Another popular statsd client for Node.js 261 | * [statsd](https://github.com/etsy/statsd) - The canonical server 262 | 263 | RELEASES 264 | -------- 265 | 266 | See the [changelog](CHANGELOG.md). 267 | 268 | LICENSE 269 | ------- 270 | 271 | ISC - see 272 | [LICENSE](https://github.com/msiebuhr/node-statsd-client/blob/master/LICENSE). 273 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v0.4.7 (2021-06-03) 2 | 3 | - [#94](https://github.com/msiebuhr/node-statsd-client/pull/94) feat\(distribution\): add support for datadog's distribution metric ([JGAntunes](mailto:joao@netlify.com)) 4 | - [#93](https://github.com/msiebuhr/node-statsd-client/pull/93) Bump hosted-git-info from 2.8.5 to 2.8.9 ([dependabot[bot]](mailto:49699333+dependabot[bot]@users.noreply.github.com)) 5 | - [#92](https://github.com/msiebuhr/node-statsd-client/pull/92) Bump lodash from 4.17.19 to 4.17.21 ([dependabot[bot]](mailto:49699333+dependabot[bot]@users.noreply.github.com)) 6 | 7 | ### v0.4.6 (2021-04-22) 8 | 9 | #### Pull requests 10 | 11 | - [#91](https://github.com/msiebuhr/node-statsd-client/pull/91) update use of Buffer ([khuang](mailto:khuang@havenlife.com)) 12 | - [#87](https://github.com/msiebuhr/node-statsd-client/pull/87) Bump lodash from 4.17.11 to 4.17.19 ([dependabot[bot]](mailto:49699333+dependabot[bot]@users.noreply.github.com)) 13 | - [#85](https://github.com/msiebuhr/node-statsd-client/pull/85) Add automatically generated changelog ([Andreas Lind](mailto:andreas.lind@peakon.com)) 14 | 15 | #### Commits to master 16 | 17 | - [TCPSocket: Fix spelling \(\#86\)](https://github.com/msiebuhr/node-statsd-client/commit/b230cec5d688c7b84feca4760013be495e3f84e6) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 18 | 19 | ### v0.4.4 (2019-09-26) 20 | 21 | - [Package: Don't include test\/infrastructure files in NPM pakcage](https://github.com/msiebuhr/node-statsd-client/commit/68e8b91a57fdd5275df0a11e835dfbdb69257dd3) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 22 | 23 | ### v0.4.3 (2019-09-26) 24 | 25 | #### Pull requests 26 | 27 | - [#80](https://github.com/msiebuhr/node-statsd-client/pull/80) Add support for sub routers with parameterized routes ([Garret Meier](mailto:garret@mixmax.com)) 28 | 29 | #### Commits to master 30 | 31 | - [Tests: Skip flappy timing-related test](https://github.com/msiebuhr/node-statsd-client/commit/3f77b0ff931870a957e4556b13b543a1e0356581) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 32 | - [Travis: Don't test on Node 6.x \(EOL 2019-04-30\)](https://github.com/msiebuhr/node-statsd-client/commit/0edfa2347fdbb68f9b7fd00104eb1398aa3bc7db) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 33 | - [Travis: Add Node 10 and 12](https://github.com/msiebuhr/node-statsd-client/commit/ac87bf989016423287b4e7b1b52e70199ed0b418) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 34 | - [Packages: Normailze syntax && add package-lock](https://github.com/msiebuhr/node-statsd-client/commit/f5324f5141d3da8eb65604cb34d7d25567437702) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 35 | - [Switch to eslint](https://github.com/msiebuhr/node-statsd-client/commit/a15da5ed6f8e359106b70c009853eb98eca9eee8) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 36 | 37 | ### v0.4.2 38 | - [Release v0.4.2](https://github.com/msiebuhr/node-statsd-client/commit/5ad537571d768427c61bc4339e0f2c7f4c5d0f32) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 39 | - [Allow IPv6 UDP](https://github.com/msiebuhr/node-statsd-client/commit/f0815a0f4707a4ba4470b8c1fb5a828972fd8630) ([Ceesjan Luiten](mailto:ceesjan@ytec.nl)) 40 | 41 | ### v0.4.1 42 | - [Release v0.4.1](https://github.com/msiebuhr/node-statsd-client/commit/c21843a7bc8d7b67711d4a9864047dcece2232e0) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 43 | - [README: Add some external links](https://github.com/msiebuhr/node-statsd-client/commit/f7d887573f75f1866af07bfa011064e7192946de) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 44 | - [Don't test on 0.12 any more...](https://github.com/msiebuhr/node-statsd-client/commit/419330fc4c95c298a0e904539eaaa70b24e88c5c) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 45 | - [Test on Node 4, 6, 8 and latest](https://github.com/msiebuhr/node-statsd-client/commit/3df7eb439a36e1201f1ad1e272491660a5e265a0) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 46 | - [Add callback helper](https://github.com/msiebuhr/node-statsd-client/commit/b828eb675a3c3968c88b44f0741b74637039b16d) ([GochoMugo](mailto:mugo@forfuture.co.ke)) 47 | 48 | ### v0.4.0 49 | - [Release v0.4.0](https://github.com/msiebuhr/node-statsd-client/commit/98ea30571806e68a0badcdaeecfef2790977be08) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 50 | - [Merge tags with 0.12 valid functions](https://github.com/msiebuhr/node-statsd-client/commit/a0c526a3dc3b0760401a95a34c98c73a69de4fbf) ([Chris911](mailto:christophe.naud.dulude@gmail.com)) 51 | - [Update README for metric level tags](https://github.com/msiebuhr/node-statsd-client/commit/1e643efa6f2b0d526014e8c0d78e9dd2e0254d20) ([Chris911](mailto:christophe.naud.dulude@gmail.com)) 52 | - [Add metrics level tags](https://github.com/msiebuhr/node-statsd-client/commit/1bf097b0e1367dce08ca0877e0f2a8ec7508ec61) ([Chris911](mailto:christophe.naud.dulude@gmail.com)) 53 | - [Add .jshintignore](https://github.com/msiebuhr/node-statsd-client/commit/f3609d699b3b92ae88ec6332eaf6a67dc71eb1df) ([Andreas Lind](mailto:andreas@one.com)) 54 | - [+3 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.3.0...v0.4.0) 55 | 56 | ### v0.3.0 57 | #### Pull requests 58 | 59 | - [#68](https://github.com/msiebuhr/node-statsd-client/pull/68) Fixed typo in TCPSocket for \_maxBufferSize property ([Philippe Guilbault](mailto:philippe.guilbault@playster.com)) 60 | 61 | #### Commits to master 62 | 63 | - [Release v0.3.0](https://github.com/msiebuhr/node-statsd-client/commit/260f56ee29b83d39036462a87d6efb7d113fce73) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 64 | - [Add support for StatsD tags on all payloads \(\#69\)](https://github.com/msiebuhr/node-statsd-client/commit/9e7756eb046a4fa9cb2a058328bfd3ab63a5bafb) ([Brian Conn](mailto:bcconn2112@gmail.com)) 65 | - [Enable express middleware to send both url prefixed and non prefixed status code increments \(\#67\)](https://github.com/msiebuhr/node-statsd-client/commit/9cfe31b2f14ddd12016e8ff97633171e00f170d6) ([Matthew Sessions](mailto:shichongrui@gmail.com)) 66 | 67 | ### v0.2.4 68 | #### Pull requests 69 | 70 | - [#66](https://github.com/msiebuhr/node-statsd-client/pull/66) Replace anonymous functions with named functions ([Oleksandr Kureniov](mailto:s@zxc.pp.ua)) 71 | 72 | #### Commits to master 73 | 74 | - [Release v0.2.4](https://github.com/msiebuhr/node-statsd-client/commit/0aa554430ae2ced8a6f55550c61682007d076013) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 75 | 76 | ### v0.2.3 77 | #### Pull requests 78 | 79 | - [#65](https://github.com/msiebuhr/node-statsd-client/pull/65) Fix bug in how res.end is handled ([Andrea Baccega](mailto:vekexasia@gmail.com)) 80 | - [#62](https://github.com/msiebuhr/node-statsd-client/pull/62) Pass HTTP headers as documented ([Garth Kidd](mailto:garth@garthk.com)) 81 | - [#60](https://github.com/msiebuhr/node-statsd-client/pull/60) accept 0 as parameter for increment\/decrement ([Charly Koza](mailto:cka@f4-group.com)) 82 | - [#59](https://github.com/msiebuhr/node-statsd-client/pull/59) Add .raw\(\) to pass data straight through ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 83 | - [#58](https://github.com/msiebuhr/node-statsd-client/pull/58) incorrect express documentation ([Zach McGonigal](mailto:zach.mcgonigal@gmail.com)) 84 | 85 | #### Commits to master 86 | 87 | - [Release v0.2.3](https://github.com/msiebuhr/node-statsd-client/commit/eedc69059d4081d9e9013e6fe62841a5011669b6) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 88 | - [Also sets custom express-prefix on status-code](https://github.com/msiebuhr/node-statsd-client/commit/d78fca8ab9e3a0f4572f38a4aed303581e7050be) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 89 | - [Travis: Drop testing on 0.10](https://github.com/msiebuhr/node-statsd-client/commit/7a31d57a207e83ccbeee33cde4d4e92a18e769dc) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 90 | - [Document .histogram\(\)](https://github.com/msiebuhr/node-statsd-client/commit/a7e0a17c7e49a331abc3f48af1d404c4d5457022) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 91 | - [Fix indent in tests](https://github.com/msiebuhr/node-statsd-client/commit/d201aa5b4069f100b938bd445f2c5e65a3491470) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 92 | 93 | ### v0.2.2 94 | #### Pull requests 95 | 96 | - [#55](https://github.com/msiebuhr/node-statsd-client/pull/55) Add express middleware support for sub-routers ([David Volquartz Lebech](mailto:david@lebech.info)) 97 | - [#54](https://github.com/msiebuhr/node-statsd-client/pull/54) Fix function signature in comment ([Sascha Depold](mailto:sdepold@users.noreply.github.com)) 98 | 99 | #### Commits to master 100 | 101 | - [Release 0.2.2.](https://github.com/msiebuhr/node-statsd-client/commit/01acbe18ec8ae99f2cb48bff1b0ce0734d89ca04) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 102 | - [Package.json: Add license.](https://github.com/msiebuhr/node-statsd-client/commit/30470a3d1dfa38beed730839066570feeca022bb) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 103 | 104 | ### v0.2.1 105 | #### Pull requests 106 | 107 | - [#50](https://github.com/msiebuhr/node-statsd-client/pull/50) Express middleware: improve route name sanitation + added an option ([Joris Berthelot](mailto:jberthelot@profilsoft.com)) 108 | - [#51](https://github.com/msiebuhr/node-statsd-client/pull/51) Chainable API ([Thomas Hunter II](mailto:thunter@opentable.com)) 109 | 110 | #### Commits to master 111 | 112 | - [Release v0.2.1.](https://github.com/msiebuhr/node-statsd-client/commit/56e3ca32d09b717d80381cad65b7a2bc39e8de21) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 113 | - [Travis: test on 0.10, 0.12 and 4.0.](https://github.com/msiebuhr/node-statsd-client/commit/6ac7be68020bf462c10eb61c0421952e678dc17d) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 114 | 115 | ### v0.2.0 116 | #### Pull requests 117 | 118 | - [#49](https://github.com/msiebuhr/node-statsd-client/pull/49) add histogram support ([Daniel Ruehle](mailto:dan@yikyakapp.com)) 119 | 120 | #### Commits to master 121 | 122 | - [Release v0.2.0](https://github.com/msiebuhr/node-statsd-client/commit/bb1f4133ae59e5c3fd8dce4e4d12a7434266295d) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 123 | 124 | ### v0.1.0 125 | #### Pull requests 126 | 127 | - [#46](https://github.com/msiebuhr/node-statsd-client/pull/46) Add tcp support for the client ([Luis De Pombo](mailto:lfdepombo@uber.com)) 128 | 129 | #### Commits to master 130 | 131 | - [Release v0.1.0.](https://github.com/msiebuhr/node-statsd-client/commit/e5569c7ad5a0baa4c4443b49e6a26e40ce8bbdad) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 132 | 133 | ### v0.0.18 134 | #### Pull requests 135 | 136 | - [#44](https://github.com/msiebuhr/node-statsd-client/pull/44) convert EphemeralSocket.\_port explicit to integer ([Carsten Lamm](mailto:clamm@carpooling.com)) 137 | - [#45](https://github.com/msiebuhr/node-statsd-client/pull/45) EphemeralSocket should be exclusive ([James Taylor](mailto:jt@gosquared.com)) 138 | - [#43](https://github.com/msiebuhr/node-statsd-client/pull/43) add missing } to docs ([Clarkie](mailto:andrew.t.clarke@gmail.com)) 139 | 140 | #### Commits to master 141 | 142 | - [Release 0.0.18.](https://github.com/msiebuhr/node-statsd-client/commit/afce5cee45cc661b44bf3e70a86e575a5b23117b) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 143 | 144 | ### v0.0.17 145 | #### Pull requests 146 | 147 | - [#42](https://github.com/msiebuhr/node-statsd-client/pull/42) Metrics over http ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 148 | 149 | #### Commits to master 150 | 151 | - [Release v0.0.17.](https://github.com/msiebuhr/node-statsd-client/commit/bf77ac5257297441ab614ee8429b2941942daebc) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 152 | 153 | ### v0.0.16 (2014-08-24) 154 | 155 | #### Pull requests 156 | 157 | - [#38](https://github.com/msiebuhr/node-statsd-client/pull/38) Update statsd multi-metric-packets link ([Piron Tristan](mailto:piront@users.noreply.github.com)) 158 | - [#40](https://github.com/msiebuhr/node-statsd-client/pull/40) Add delta gauges. Fixes \#34. ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 159 | - [#41](https://github.com/msiebuhr/node-statsd-client/pull/41) Update travis to use modern Node.js. ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 160 | - [#37](https://github.com/msiebuhr/node-statsd-client/pull/37) Fix typo ([Thiago Caiubi](mailto:thiago.caiubi@gmail.com)) 161 | 162 | #### Commits to master 163 | 164 | - [Clean up paths in Makefile.](https://github.com/msiebuhr/node-statsd-client/commit/758c048d5bb17293ed64a3ec5d5903ed090dfea2) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 165 | - [Remove prepublish step.](https://github.com/msiebuhr/node-statsd-client/commit/004483c946bf47b2313d201b8f7ba06fffed9505) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 166 | - [README: Fix quick tour.](https://github.com/msiebuhr/node-statsd-client/commit/57fc8fd1840fb9603d25ddab29348ee62b9cbdab) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 167 | 168 | ### v0.0.15 169 | - [Release v0.0.15.](https://github.com/msiebuhr/node-statsd-client/commit/5dd31a4e5dbebe43fc1fdd7a2213d02881dec1b5) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 170 | - [Tests: Fix regex that would cause erroneous test failures.](https://github.com/msiebuhr/node-statsd-client/commit/6b2f9537e585ab22315071fbc0b2a254ae37f2c5) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 171 | - [fixed issue with orphaned setInterval in EphemeralSocket](https://github.com/msiebuhr/node-statsd-client/commit/2273d5a222ddf365810183566fd068f3a63484b7) ([Doug Daniels](mailto:daniels.douglas@gmail.com)) 172 | - [Remove stream-helpers from README.](https://github.com/msiebuhr/node-statsd-client/commit/f7674f35a8285ac0f853eedc8531c1c2490dd401) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 173 | 174 | ### v0.0.14 175 | - [Release v0.0.14.](https://github.com/msiebuhr/node-statsd-client/commit/e796d376fc4f73e657d56918d7e98ec3c7878a75) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 176 | - [First cut of buffering metrics & send in bulk.](https://github.com/msiebuhr/node-statsd-client/commit/ecfad72a3bf817e0ac53521aeb6d4690649b6d26) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 177 | 178 | ### v0.0.13 179 | - [Release v0.0.13.](https://github.com/msiebuhr/node-statsd-client/commit/5880c849ccac24407df5d06476fff9fdd195e2be) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 180 | - [Remove stream-helpers, as they weren't used.](https://github.com/msiebuhr/node-statsd-client/commit/3d0802c6fedcd170839eac825e9fdb3a30bbd710) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 181 | - [Remove trailing slashes in express middleware.](https://github.com/msiebuhr/node-statsd-client/commit/ff86b04a4761717af48550aac7d87eec6199a03d) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 182 | - [Update new parameter-naming in README.md](https://github.com/msiebuhr/node-statsd-client/commit/e6a86df6af6d66d0dd044d64513cd906b921b390) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 183 | - [Fix minor syntax-error in README.md example.](https://github.com/msiebuhr/node-statsd-client/commit/3ededc3f7f1016b562e09c281f51d40503593f0b) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 184 | - [+4 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.0.12...v0.0.13) 185 | 186 | ### v0.0.12 187 | - [Release v0.0.12.](https://github.com/msiebuhr/node-statsd-client/commit/57efb93afc755f8331c8fd6df106d7481e6bf28f) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 188 | - [Properly CamelCase names in EphemeralSocket.](https://github.com/msiebuhr/node-statsd-client/commit/f96e28867ce02d9e858b0a658e99256151ee5c27) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 189 | - [Report express response-codes under `response\_code`.](https://github.com/msiebuhr/node-statsd-client/commit/ff5016881d5b646e50156346c6b62e897df17a26) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 190 | - [Fix corner-cases in Express per-route key reporting.](https://github.com/msiebuhr/node-statsd-client/commit/e760b6f33c7a6f718559b00e1c2d1c052b0a53f6) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 191 | - [Fix issue with rapid series of errors on socket.](https://github.com/msiebuhr/node-statsd-client/commit/25c69071baa8a1652800ce823a5e7c637936ff87) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 192 | - [+1 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.0.11...v0.0.12) 193 | 194 | ### v0.0.11 195 | - [Release v0.0.11.](https://github.com/msiebuhr/node-statsd-client/commit/163fc3ed135161dd077bffc971fb1e7d5dfad41c) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 196 | - [Allow custom URL-aliases in Express-middleware.](https://github.com/msiebuhr/node-statsd-client/commit/b1175c370fc25f7b99f25d003a3f9df701008444) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 197 | 198 | ### v0.0.10 199 | - [Release v0.0.10.](https://github.com/msiebuhr/node-statsd-client/commit/e64f06ff0cd48303706d6bb74da4b9f508ecffbf) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 200 | - [getExpressMiddleware helper: Avoid calling undefined method when req.route.name is a RegExp.](https://github.com/msiebuhr/node-statsd-client/commit/0a12e20163e05a0eddb2051af1e87bf0e77a513d) ([Andreas Lind Petersen](mailto:andreas@one.com)) 201 | 202 | ### v0.0.9 203 | - [Release v0.0.9.](https://github.com/msiebuhr/node-statsd-client/commit/e1361287f4d7fc19ced6fbe3d69b16aff30a07b2) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 204 | - [JSHint: Fix complaints in testing.](https://github.com/msiebuhr/node-statsd-client/commit/7d07f2135e4f70921b7523311f8ccedcc0c1fe7f) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 205 | - [getExpressMiddleware helper: Also report the request method as part of the key when timeByUrl is turned on.](https://github.com/msiebuhr/node-statsd-client/commit/da402ba837254ae47dccbaeab75f4c1274841f3d) ([Andreas Lind Petersen](mailto:andreas@one.com)) 206 | - [Close test-server after use. Fixes \#22.](https://github.com/msiebuhr/node-statsd-client/commit/6be275f550e881c09ef7aa6e100ab546d902fdda) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 207 | 208 | ### v0.0.8 209 | - [Release v0.0.8.](https://github.com/msiebuhr/node-statsd-client/commit/a9a0cca845610fb457cd3a3ba0f3e99be7b2a317) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 210 | - [Prolong timeout in close\(\)-tests, to avoid races.](https://github.com/msiebuhr/node-statsd-client/commit/2ed261b9bcfa0ffbf79bca3ca4c191496572fb51) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 211 | - [Fix JSHint error \/ clean up unit-tests a wee bit.](https://github.com/msiebuhr/node-statsd-client/commit/4418b853e9b9b95a75c2ddc2be5cb95bc268d4f9) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 212 | - [Adding in a test case](https://github.com/msiebuhr/node-statsd-client/commit/bb3781c1b8e45ca04a5c309b228434c4e12a7d7b) ([Albert Wang](mailto:albertwang@ironclad.mobi)) 213 | - [Style conformity](https://github.com/msiebuhr/node-statsd-client/commit/75c0195df47281a0669f703bf7331fb56020a932) ([Albert Wang](mailto:albertwang@ironclad.mobi)) 214 | - [+4 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.0.7...v0.0.8) 215 | 216 | ### v0.0.7 217 | - [Release v0.0.7.](https://github.com/msiebuhr/node-statsd-client/commit/80cf6c24db63f8999b94c4f347cdd5edda9ef513) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 218 | - [Test timeouts in EphemeralSocket.](https://github.com/msiebuhr/node-statsd-client/commit/2b93d6e247cd3f8f742d410178e89feea725fad2) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 219 | - [Test on 'raw' EphemeralSocket in ditto tests.](https://github.com/msiebuhr/node-statsd-client/commit/b7a8e71668e476434c770e119f21aa3b92e18ce2) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 220 | - [Fix inconsistent naming in ephemeral socket.](https://github.com/msiebuhr/node-statsd-client/commit/424b05086d2ff9db33359cc2eb3e3b2fbe96e073) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 221 | - [Close underlying socket on error.](https://github.com/msiebuhr/node-statsd-client/commit/3381ca09534f3af82c42610700133a316264ee71) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 222 | - [+3 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.0.6...v0.0.7) 223 | 224 | ### v0.0.6 225 | - [Release v0.0.6.](https://github.com/msiebuhr/node-statsd-client/commit/af7363ee646bdeed7a4eba50378d9501e04d9924) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 226 | - [Remove extraneous layer of nesting in unit-tests.](https://github.com/msiebuhr/node-statsd-client/commit/2c9077ad68f9cb1b58636c0d56f7e732efdfd385) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 227 | - [Switch to using dummy server for all tests.](https://github.com/msiebuhr/node-statsd-client/commit/7560f6872577789cf2597150c3d9b0aa5d1c34d9) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 228 | - [Re-fix async socket creation \(issues \#4 + \#18\).](https://github.com/msiebuhr/node-statsd-client/commit/5e1abaf05552891fa0bbe800623f125e73c08b75) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 229 | - [Fix accidental removal 'jshint' instead of 'vows' in e8b6f97.](https://github.com/msiebuhr/node-statsd-client/commit/92f2ee9f0825454fe7b8a95bc18c2686511abd64) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 230 | - [+2 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.0.5...v0.0.6) 231 | 232 | ### v0.0.5 233 | #### Pull requests 234 | 235 | - [#15](https://github.com/msiebuhr/node-statsd-client/pull/15) Merge route-specific timings in express-middleware. ([Addison Higham](mailto:addisonj@gmail.com)) 236 | - [#12](https://github.com/msiebuhr/node-statsd-client/pull/12) Fix check for trailing dot in prefix. ([Roman Bolgov](mailto:bolgovr@gmail.com)) 237 | 238 | #### Commits to master 239 | 240 | - [Release v0.0.5.](https://github.com/msiebuhr/node-statsd-client/commit/32455c43c557360fb4584a06cdf451f164955e56) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 241 | - [Allow ExpressMiddleware without extra prefix](https://github.com/msiebuhr/node-statsd-client/commit/7ac596f808aafae9d29fb9abf289921b20289981) ([Ole Michaelis](mailto:Ole.Michaelis@googlemail.com)) 242 | - [Added docs](https://github.com/msiebuhr/node-statsd-client/commit/ee25cf4bd754a2872ee36a9dee2267854548bc40) ([Ole Michaelis](mailto:Ole.Michaelis@googlemail.com)) 243 | - [Tests for statsd sets](https://github.com/msiebuhr/node-statsd-client/commit/c53df92356fab6d3b5a68175d6c2d3bae296e063) ([Ole Michaelis](mailto:Ole.Michaelis@googlemail.com)) 244 | - [Support for statsd sets](https://github.com/msiebuhr/node-statsd-client/commit/7af85c3be22aab8399c13047748eee7ea1a46241) ([Ole Michaelis](mailto:Ole.Michaelis@googlemail.com)) 245 | - [+17 more](https://github.com/msiebuhr/node-statsd-client/compare/v0.0.4...v0.0.5) 246 | 247 | ### v0.0.4 248 | - [v0.0.4.](https://github.com/msiebuhr/node-statsd-client/commit/61e4fb28f92c2eefe298a6d466d9c27ee0429b6f) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 249 | - [Update README with info on measureStream-functions.](https://github.com/msiebuhr/node-statsd-client/commit/4442bf641ce053f0cc74b20ac1d3a90cad9a9bc2) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 250 | - [Try watching streams using, well, streams.](https://github.com/msiebuhr/node-statsd-client/commit/90cd37d4d0051bcc02cb3b27cabc436bf9b5ebc4) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 251 | - [Initial work on Stream-helpers \(see \#9\).](https://github.com/msiebuhr/node-statsd-client/commit/1a9aeed8488c89ce0c869bfe1d1b6626b58a3dd8) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 252 | 253 | ### v0.0.3 254 | - [v0.0.3](https://github.com/msiebuhr/node-statsd-client/commit/2f681d52c405737ac1e65f34415e013e85a93526) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 255 | - [Clean up README.](https://github.com/msiebuhr/node-statsd-client/commit/bcb1f37f9406bf21c84e7e24cfd275dd04d587b1) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 256 | - [Add child-clients with sub-prefixes.](https://github.com/msiebuhr/node-statsd-client/commit/b84a437f738c7452158eede7118b201c1cc16e9b) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 257 | - [Split out socket-handling.](https://github.com/msiebuhr/node-statsd-client/commit/ee8c141039e3637c470eaf65608480fd554c9d7f) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 258 | - [Change how hostname is configured.](https://github.com/msiebuhr/node-statsd-client/commit/6709e954ff104a377596dd7f244f4d8e6e71ffc9) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 259 | 260 | ### v0.0.2 261 | - [Release v0.0.2.](https://github.com/msiebuhr/node-statsd-client/commit/4b62a34fda7370db0d8701a1daf64869c4f0169d) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 262 | - [Add a trivial example.](https://github.com/msiebuhr/node-statsd-client/commit/e35a1e546c25c226f671efa3ca45d5fbdc99537b) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 263 | - [Add support for optional prefix.](https://github.com/msiebuhr/node-statsd-client/commit/43e66fe264ebe759c148d906c9b5f5b3be3da2c4) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 264 | 265 | ### v0.0.1 266 | - [Release v0.0.1.](https://github.com/msiebuhr/node-statsd-client/commit/ed5075e14b867f333b600e0b1a9dce01e70e20ae) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 267 | - [Fix .close\(\) cutting off most recent stats.](https://github.com/msiebuhr/node-statsd-client/commit/a58331eb8eb76319c8fb4dd770d3ecaf9cfce208) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 268 | - [Option for disabling the timer.](https://github.com/msiebuhr/node-statsd-client/commit/069de0a541c0396af6976af7ecaa03655a0ec5ee) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 269 | - [Add explicit close\(\) to stop. Fixes \#2.](https://github.com/msiebuhr/node-statsd-client/commit/0d87595f625ed49cab46abd10f47485c20487075) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 270 | - [Minor code-cleanup and comment a bit.](https://github.com/msiebuhr/node-statsd-client/commit/e995ed70f13789eb6cd889f89591513c6a114a1f) ([Morten Siebuhr](mailto:sbhr@sbhr.dk)) 271 | - [+7 more](https://github.com/msiebuhr/node-statsd-client/compare/ed5075e14b867f333b600e0b1a9dce01e70e20ae%5E...v0.0.1) 272 | 273 | --------------------------------------------------------------------------------