diff --git a/README.md b/README.md index d19731c..ef1e330 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ A node.js client for extended StatsD server of [Datadog](http://www.datadoghq.com). -Datadog added new some features(histogram and tags) to their own StatsD implementation. +Datadog added some new features (histogram, tags, and event) to their own StatsD implementation. This client is an extension of general StatsD client to work with that server. Most parts of codes came from [Steve Ivy](https://github.com/sivy)'s [node-statsd](https://github.com/sivy/node-statsd). -I just added few lines to support datadog's histogram and tags features. +I just added few lines to support datadog's extensions. Some of these changes (histogram and tags) have now made it back into node-statsd. The name of the package is changed because this isn't really statsd client and should be able to be used with original statsd client. @@ -23,6 +23,25 @@ The name of the package is changed because this isn't really statsd client and s > c.histogram('node_test.some_service.data', 100) // works only with datadog' StatsD > c.increment('node_test.int', 1, ['tag:one']) // works only with datadog' StatsD +### Event Support + +This library supports raising events to a dogstatsd server as well. The convention is: + + client.event(title, description, options, tags) + +Options is an object that contains one of three optional parameters that can be set on the event: + +* *eventType* - There is an enum called eventType that wraps the four options (error, info, success, warning). Default is info. +* *priority* - String that sets priority. There is an enum called priority that wraps the two options (low, normal). Default is normal. +* *aggKey* - String that allows the event viewer to aggregate similar requests. + +#### Example Call + + var dogstatd = require('../lib/statsd.js'); + var client = new dogstatd.StatsD('localhost',8125); + + client.event('Error Detected','error description',{eventType: dogstatd.eventType.ERROR, priority: dogstatd.priority.NORMAL, aggKey: 'Error'},['nodeApp']); + ## License node-statsd is licensed under the MIT license. diff --git a/lib/statsd.js b/lib/statsd.js index dbaffaf..a31da5b 100644 --- a/lib/statsd.js +++ b/lib/statsd.js @@ -85,6 +85,32 @@ Client.prototype.update_stats = function(stats, delta, sampleRate, tags) { self.send(data, sampleRate, tags); }; +Client.prototype.event = function(title, text, options, tags) { + var self = this; + + var titleLength = title.length; + var textLength = text.length; + + var eventString = '_e{' + titleLength + ',' + textLength + '}:'; + eventString += title + '|' + text; + + //Start of optional parameters + if(options) { + if (options.priority) { + eventString += '|p:' + options.priority; + } + if (options.eventType) { + eventString += '|t:' + options.eventType; + } + if (options.aggKey) { + eventString += '|k:' + options.aggKey; + } + } + eventString += generateTagString(this.global_tags, tags); + + self.send_data(new Buffer(eventString)); +}; + // An internal function update the last time the socket was // used. This function is called when the socket is used // and causes demand allocated ephemeral sockets to be closed @@ -145,29 +171,43 @@ Client.prototype.send = function(data, sample_rate, tags) { else sampled_data = data; - if (this.global_tags || tags) { - var merged_tags = []; - - if (Array.isArray(this.global_tags)) - merged_tags = merged_tags.concat(this.global_tags); + var tagString = generateTagString(this.global_tags, tags); + for (stat in sampled_data) { + sampled_data[stat] = sampled_data[stat] + tagString; - if (Array.isArray(tags)) - merged_tags = merged_tags.concat(tags); - - if (merged_tags.length > 0) { - var merged_tags_str = merged_tags.join(','); - for (stat in sampled_data) - sampled_data[stat] = sampled_data[stat] + "|#" + merged_tags_str; - } } + for (var stat in sampled_data) { var send_data = stat + ":" + sampled_data[stat]; this.send_data(new Buffer(send_data)); } }; +function generateTagString(globalTags, tags) { + + if (globalTags || tags) { + var merged_tags = []; + + if (Array.isArray(globalTags)) + merged_tags = merged_tags.concat(globalTags); + + + if (Array.isArray(tags)) + merged_tags = merged_tags.concat(tags); + + if (merged_tags.length > 0) { + return '|#' + merged_tags.join(','); + } + else { + return ''; + } + } + //catch all cases where nothing is returned. + return ''; +} + Client.prototype.close = function() { if (this.socket) this.socket.close(); @@ -182,3 +222,16 @@ Client.prototype.close = function() { }; exports.StatsD = Client; + +//Enum types to support events generation +exports.priority = { + NORMAL: 'normal', + LOW: 'low' +}; + +exports.eventType = { + ERROR: 'error', + WARNING: 'warning', + INFO: 'info', + SUCCESS: 'success' +}; diff --git a/package.json b/package.json index 5157519..d634b33 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "engines": { "node": ">=0.1.97" }, + "scripts": {"test": "mocha"}, "licenses": [ { "type": "MIT", diff --git a/test/dogstatsd_events.spec.js b/test/dogstatsd_events.spec.js new file mode 100644 index 0000000..c6a84e5 --- /dev/null +++ b/test/dogstatsd_events.spec.js @@ -0,0 +1,72 @@ +/** + * Tests basic events functionality + */ + +var assert = require('assert'); +var sinon = require('sinon'); +var dogstatsd = require('../lib/statsd.js'); + +describe('Dogstatsd Events Functionality', function() { + describe('Dogstatd Event Calls', function() { + var client, sendSpy; + + before(function() { + client = new dogstatsd.StatsD('localhost',8125); + //replace send with spy to test without udp. + sendSpy = sinon.spy(); + client.send_data = sendSpy; + }); + + afterEach(function() { + sendSpy.reset(); + }); + + after(function() { + client.close(); + }); + + it('should send the basic string based on core options', function() { + client.event('TestTitle', 'TestText'); + var spyBuffer = sendSpy.args[0][0]; + assert('_e{9,8}:TestTitle|TestText' === spyBuffer.toString()); + }); + + it('should include a priority if included in the options', function() { + client.event('TestTitle', 'TestText', {priority: dogstatsd.priority.NORMAL}); + var spyBuffer = sendSpy.args[0][0]; + assert(/\|p:normal/.test(spyBuffer)); + }); + + it('should include an event type if included in the options', function() { + client.event('TestTitle', 'TestText', {eventType: dogstatsd.eventType.SUCCESS}); + var spyBuffer = sendSpy.args[0][0]; + assert(/\|t:success/.test(spyBuffer)); + }); + + it('should include an aggregation key if included in the options', function() { + client.event('TestTitle', 'TestText', {aggKey: 'testkey'}); + var spyBuffer = sendSpy.args[0][0]; + assert(/\|k:testkey/.test(spyBuffer)); + }); + + it('should include tags seperated by commas after a |#', function() { + client.event('TestTitle', 'TestText', {aggKey: 'testkey'},['tag1:test','tag2:test2','tag3']); + var packet = sendSpy.args[0][0]; + assert(/\|#tag1:test,tag2:test2,tag3/.test(packet)); + }); + }); + + describe('Event Enums', function() { + it('should translate enumerated types to correct strings for event priorities', function() { + assert.equal(dogstatsd.priority.NORMAL, 'normal'); + assert.equal(dogstatsd.priority.LOW, 'low'); + }); + + it('should translate enumerated types to correct strings for event types', function() { + assert.equal(dogstatsd.eventType.ERROR,'error'); + assert.equal(dogstatsd.eventType.WARNING,'warning'); + assert.equal(dogstatsd.eventType.INFO,'info'); + assert.equal(dogstatsd.eventType.SUCCESS,'success'); + }); + }); +}); \ No newline at end of file diff --git a/test/dogstatsd_metrics.spec.js b/test/dogstatsd_metrics.spec.js new file mode 100644 index 0000000..2af3152 --- /dev/null +++ b/test/dogstatsd_metrics.spec.js @@ -0,0 +1,42 @@ +/** + * Tests basic increment functionality + */ +var assert = require('assert'); +var sinon = require('sinon'); +var dogstatsd = require('../lib/statsd.js'); + +describe('dogstatsd Metrics Functionality: ', function() { + describe('Tags Functionality', function() { + var client, sendSpy; + + before(function() { + client = new dogstatsd.StatsD('localhost',8125); + //replace send with spy to test without udp. + sendSpy = sinon.spy(); + client.send_data = sendSpy; + }); + + afterEach(function() { + sendSpy.reset(); + }); + + after(function() { + client.close(); + }); + + it('should include tags separated by commas after a |#', function() { + client.increment('node_test.int',1,['tag1:test','tag2:test2','tag3']); + var packet = sendSpy.args[0][0]; + assert(/\|#tag1:test,tag2:test2,tag3/.test(packet)); + + }); + + it('should not include the tags section if no tags are specified', function() { + client.increment('node_test.int',1); + var packet = sendSpy.args[0][0]; + console.log(packet.toString()); + assert.equal(packet.toString(),'node_test.int:1|c'); + + }); + }); +}); \ No newline at end of file