diff --git a/.gitignore b/.gitignore index daec33a..2431652 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules npm-debug.log +.nyc_output diff --git a/LICENSE b/LICENSE index 7f7dc04..10daf38 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -node-graylog (C) 2011 Egor Egorov +MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/README.md b/README.md index cbbb558..feb411b 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,105 @@ -# node-graylog2 -[![NPM version](http://img.shields.io/npm/v/graylog2.svg?style=flat-square)](https://www.npmjs.org/package/graylog2) [![NPM license](http://img.shields.io/npm/l/graylog2.svg?style=flat-square)](https://www.npmjs.org/package/graylog2) +# graylog2 -Graylog2 client library for Node.js, based on node-graylog. This -has been heavily modified to the point where there is not much left -of the original; however, this library should still be compatible -with the old one, except for configuration and the GLOBAL function setup -(some optional arguments in logging calls are not supported; they will be -logged as additional data). +[![NPM version](http://img.shields.io/npm/v/graylog2.svg?style=flat-square)](https://www.npmjs.org/package/graylog2) +[![NPM license](http://img.shields.io/npm/l/graylog2.svg?style=flat-square)](https://www.npmjs.org/package/graylog2) -** New: ** Chunked [GELF](https://github.com/Graylog2/graylog2-docs/wiki/GELF) -is now supported. +Graylog2 client library for Node.js -## Synopsis +## Installation + +```sh +npm install graylog2 --save +``` + +## What is Graylog? + +Graylog is popular logging software. You can get it at http://www.graylog2.org. Incidentally, since this package +uses Graylog's [GELF](http://docs.graylog.org/en/latest/pages/gelf.html) protocol, you can also use this with other +logging software that has GELF support. + +## Usage ### Available functions -* graylog.emergency -* graylog.alert -* graylog.critical -* graylog.error -* graylog.warning -* graylog.notice -* graylog.info -* graylog.debug +* graylog.emergency(short, full, fields, timestamp) +* graylog.alert(short, full, fields, timestamp) +* graylog.critical(short, full, fields, timestamp) +* graylog.error(short, full, fields, timestamp) +* graylog.warning(short, full, fields, timestamp) +* graylog.notice(short, full, fields, timestamp) +* graylog.info(short, full, fields, timestamp) +* graylog.debug(short, full, fields, timestamp) + +Arguments: + +- short (string): A short message to log. +- full (string, optional): Additional details. +- fields (object, optional): An object of key/value pairs to help with filtering. +- timestamp (integer, optional): A custom timestamp (milliseconds). ### Code snippets -```javascript -var graylog2 = require("graylog2"); -var logger = new graylog2.graylog({ +```js +var Graylog = require('graylog2'); +var logger = new Graylog({ servers: [ - { 'host': 127.0.0.1, port: 12201 }, - { 'host': 127.0.0.2, port: 12201 } + { host: '127.0.0.1', port: 12201 }, + { host: '127.0.0.2', port: 12201 } ], - hostname: 'server.name', // the name of this host - // (optional, default: os.hostname()) - facility: 'Node.js', // the facility for these log messages - // (optional, default: "Node.js") - bufferSize: 1350 // max UDP packet size, should never exceed the - // MTU of your system (optional, default: 1400) + hostname: 'server.name', // the name of this host (optional, default: os.hostname()) + facility: 'Node.js', // the facility for these log messages (optional, default: "Node.js") + bufferSize: 1350 // max UDP packet size, should not exceed the MTU of your network (optional, default: 1400) }); logger.on('error', function (error) { - console.error('Error while trying to write to graylog2:', error); + console.error('Error while trying to write to Graylog2:', error); }); +logger.on('warning', function (error) { + console.error('Non-fatal error while trying to write to Graylog2:', error); +}); ``` Short message: -```javascript -logger.log("What we've got here is...failure to communicate"); +```js +logger.debug("What we've got here is...failure to communicate"); ``` Long message: -```javascript -logger.log("What we've got here is...failure to communicate", "Some men you just - can't reach. So you get what we had here last week, which is the way he wants - it... well, he gets it. I don't like it any more than you men."); +```js +var short = "What we've got here is...failure to communicate"; +var long = "Some men you just can't reach. So you get what we had here last week, " + + "which is the way he wants it... well, he gets it. I don't like it any more than you men."; + +logger.debug(short, long); ``` Short with additional data: -```javascript -logger.log("What we've got here is...failure to communicate", { cool: 'beans' }); +```js +logger.debug("What we've got here is...failure to communicate", { cool: 'beans' }); ``` Long with additional data: -```javascript -logger.log("What we've got here is...failure to communicate", "Some men you just - can't reach. So you get what we had here last week, which is the way he wants - it... well, he gets it. I don't like it any more than you men.", - { - cool: "beans" - } -); -``` +```js +var short = "What we've got here is...failure to communicate"; +var long = "Some men you just can't reach. So you get what we had here last week, " + + "which is the way he wants it... well, he gets it. I don't like it any more than you men."; -Flush all log messages and close down: -```javascript -logger.close(function(){ - console.log('All done - cookie now?'); - process.exit(); -}); +logger.debug(short, long, { cool: 'beans' }); ``` -## Example - -See `test.js`. +Send all pending log messages and close the socket: -## What is graylog2 after all? - -It's a miracle. Get it at http://www.graylog2.org/ +```js +logger.close(function () { + console.log('All done!'); +}); +``` -## Installation +### More examples - npm install graylog2 +See the files in the `test` folder. diff --git a/bench.js b/bench.js index 3350313..6f750de 100644 --- a/bench.js +++ b/bench.js @@ -1,8 +1,8 @@ -var Graylog = require('./graylog').graylog; +var Graylog = require('.').graylog; var fs = require('fs'); var client; var servers = [ - { 'host': '127.0.0.1', 'port': 12201 } + { host: '127.0.0.1', port: 12201 } ]; function createClient() { @@ -27,25 +27,24 @@ console.log(''); function log(str, label, i, n, cb) { if (i === 0) { + createClient(); console.time(label + ' x' + n); - createClient(); - } - if (i === n) { - client.close(function () { - console.timeEnd(label + ' x' + n); + client.on('drain', function () { + console.timeEnd(label + ' x' + n); - console.log('Sent:', client.sent, '- Compressed:', client.compressed); - console.log(''); + console.log('Sent:', client.sent, '- Compressed:', client.compressed); + console.log(''); - if (client.sent !== n) { - throw new Error('Should have sent: ' + n); - } + if (client.sent !== n) { + throw new Error('Should have sent: ' + n); + } - cb(); - }); + cb(); + }); + } - } else { + if (i < n) { client.log('test', str); process.nextTick(log, str, label, i + 1, n, cb); } @@ -64,9 +63,9 @@ function testBigAndRandom(cb) { } function end() { + console.log('Complete.'); + console.log('Please check your logging service and verify that insertion was successful.'); console.log(''); - console.log('Insertion complete. Please check', 'http://' + servers[0].host + ':3000', 'and verify that insertion was successfull'); - console.log(''); } testSmall(function () { diff --git a/index.js b/index.js new file mode 100644 index 0000000..586d787 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/Graylog'); diff --git a/graylog.js b/lib/Graylog.js similarity index 55% rename from graylog.js rename to lib/Graylog.js index 4348117..606b171 100644 --- a/graylog.js +++ b/lib/Graylog.js @@ -3,46 +3,9 @@ var crypto = require('crypto'); var dgram = require('dgram'); var util = require('util'); var EventEmitter = require('events').EventEmitter; -var assert = require('assert'); -var MAX_SAFE_INT = 9007199254740991; // Number.MAX_SAFE_INTEGER - - -function Queue() { - this.first = null; - this.last = null; -} - - -Queue.prototype.append = function (obj) { - if (this.last) { - this.last.next = obj; - this.last = obj; - } else { - this.first = this.last = obj; - } -}; - - -Queue.prototype.getOne = function () { - var result = this.first; - - if (result) { - this.first = result.next; - result.next = null; - - if (result === this.last) { - this.last = null; - } - } - - return result; -}; - - -Queue.prototype.isEmpty = function () { - return this.last === null; -}; +var Queue = require('./Queue'); +var MAX_SAFE_INT = 9007199254740991; // Number.MAX_SAFE_INTEGER /** * Graylog instances emit errors. That means you really really should listen for them, @@ -52,28 +15,22 @@ Queue.prototype.isEmpty = function () { function Graylog(config) { EventEmitter.call(this); - this.config = config; - - this.servers = config.servers; - this.client = null; - this.hostname = config.hostname || require('os').hostname(); - this.facility = config.facility || 'Node.js'; - this.deflate = config.deflate || 'optimal'; - assert( - this.deflate === 'optimal' || this.deflate === 'always' || this.deflate === 'never', - 'deflate must be one of "optimal", "always", or "never". was "' + this.deflate + '"'); - - this._bufferSize = config.bufferSize || this.DEFAULT_BUFFERSIZE; + // settings (all safe to change at runtime through these setters) + this.setServers(config.servers); + this.setHostname(config.hostname || require('os').hostname()); + this.setFacility(config.facility || 'Node.js'); + this.setBufferSize(config.bufferSize || this.DEFAULT_BUFFERSIZE); + this.setDeflate(config.deflate || 'optimal'); // state + this._client = null; this._serverIterator = 0; this._headerPool = []; this._isDeflating = false; this._isSending = false; - this.sendQueue = new Queue(); - this.deflateQueue = this.deflate === 'never' ? null : new Queue(); - this.alwaysDeflate = this.deflate === 'always'; + this._sendQueue = new Queue(); + this._deflateQueue = new Queue(); // stats this.sent = 0; @@ -96,93 +53,163 @@ Graylog.prototype.level = { }; -Graylog.prototype.getServer = function () { - if (this.servers.length === 1) { +Graylog.prototype.setServers = function (servers) { + if (!Array.isArray(servers)) { + throw new TypeError('Servers must be an array'); + } + + if (servers.length === 0) { + throw new Error('Servers array cannot be empty'); + } + + for (var i = 0; i < servers.length; i += 1) { + var server = servers[i]; + if (!server || typeof server !== 'object') { + throw new TypeError('A server entry must be an object with "host" and "port" properties'); + } + + if (!server.hasOwnProperty('host') || !server.hasOwnProperty('port')) { + throw new TypeError('A server entry must be an object with "host" and "port" properties'); + } + + if (typeof server.host !== 'string') { + throw new TypeError('A server host must be a string'); + } + + if (typeof server.port !== 'number') { + throw new TypeError('A server port must be a number'); + } + } + + this._servers = servers; +}; + + +Graylog.prototype.setHostname = function (hostname) { + if (typeof hostname !== 'string') { + throw new TypeError('Host name must be a string'); + } + + this._hostname = hostname; +}; + + +Graylog.prototype.setFacility = function (facility) { + if (typeof facility !== 'string') { + throw new TypeError('Facility must be a string'); + } + + this._facility = facility; +}; + + +Graylog.prototype.setBufferSize = function (bufferSize) { + if (typeof bufferSize !== 'number') { + throw new TypeError('Buffer size must be a number'); + } + + this._bufferSize = bufferSize; +}; + + +Graylog.prototype.setDeflate = function (deflate) { + if (deflate !== 'optimal' && deflate !== 'always' && deflate !== 'never') { + throw new Error('deflate must be "optimal", "always", or "never". was "' + deflate + '"'); + } + + this._neverDeflate = deflate === 'never'; + this._alwaysDeflate = deflate === 'always'; +}; + + +Graylog.prototype._getServer = function () { + if (this._servers.length === 1) { // common case - return this.servers[0]; + return this._servers[0]; } - this._serverIterator += 1; - if (this._serverIterator >= MAX_SAFE_INT) { this._serverIterator = 0; } - return this.servers[this._serverIterator % this.servers.length]; + return this._servers[this._serverIterator++ % this._servers.length]; }; -Graylog.prototype.getClient = function () { - if (!this.client) { - this.client = dgram.createSocket('udp4'); +Graylog.prototype._getClient = function () { + if (!this._client) { + this._client = dgram.createSocket('udp4'); - var that = this; + this._client.unref(); - this.client.on('error', function (error) { + var that = this; + + this._client.on('error', function (error) { + // When a callback is passed to client.send(), this event does not fire. that.emit('error', error); }); } - return this.client; + return this._client; }; Graylog.prototype.destroy = function () { - this.sendQueue = null; - this.deflateQueue = null; + this._sendQueue = null; + this._deflateQueue = null; this._headerPool = []; this._isDeflating = false; this._isSending = false; - if (this.client) { - this.client.close(); - this.client.removeAllListeners(); - this.client = null; + if (this._client) { + this._client.close(); + this._client.removeAllListeners(); + this._client = null; } }; Graylog.prototype.emergency = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.EMERG); + this._log(this.level.EMERG, short, full, fields, timestamp); }; Graylog.prototype.alert = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.ALERT); + this._log(this.level.ALERT, short, full, fields, timestamp); }; Graylog.prototype.critical = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.CRIT); + this._log(this.level.CRIT, short, full, fields, timestamp); }; Graylog.prototype.error = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.ERROR); + this._log(this.level.ERROR, short, full, fields, timestamp); }; Graylog.prototype.warning = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.WARNING); + this._log(this.level.WARNING, short, full, fields, timestamp); }; -Graylog.prototype.warn = Graylog.prototype.warning; Graylog.prototype.notice = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.NOTICE); + this._log(this.level.NOTICE, short, full, fields, timestamp); }; Graylog.prototype.info = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.INFO); + this._log(this.level.INFO, short, full, fields, timestamp); }; -Graylog.prototype.log = Graylog.prototype.info; - Graylog.prototype.debug = function (short, full, fields, timestamp) { - return this._log(short, full, fields, timestamp, this.level.DEBUG); + this._log(this.level.DEBUG, short, full, fields, timestamp); }; +Graylog.prototype.log = Graylog.prototype.info; +Graylog.prototype.warn = Graylog.prototype.warning; + -function serialize(hostname, facility, short, full, fields, timestamp, level) { +Graylog.prototype._serialize = function (level, short, full, fields, timestamp) { var message = { version: '1.0', timestamp: parseInt((timestamp || Date.now()) / 1000, 10), - host: hostname, - facility: facility, + host: this._hostname, + facility: this._facility, level: level, short_message: short === null ? undefined : short, full_message: full === null ? undefined : full @@ -202,7 +229,7 @@ function serialize(hostname, facility, short, full, fields, timestamp, level) { } return new Buffer(JSON.stringify(message), 'utf8'); -} +}; Graylog.prototype._getHeadersFromPool = function (n) { @@ -229,8 +256,8 @@ Graylog.prototype._sendChunked = function (id, message, cb) { return cb(new Error('Graylog2 message too long: ' + message.length + ' bytes')); } - var client = this.getClient(); - var server = this.getServer(); + var client = this._getClient(); + var server = this._getServer(); var headers = this._getHeadersFromPool(chunkCount); var msgOffset = 0; @@ -247,6 +274,8 @@ Graylog.prototype._sendChunked = function (id, message, cb) { // Slice out the message part var data = message.slice(msgOffset, msgOffset + maxDataSize); + this.emit('chunk', header, data, i, chunkCount, server); + if (i < chunkCount - 1) { client.send([header, data], server.port, server.host); @@ -261,24 +290,24 @@ Graylog.prototype._sendChunked = function (id, message, cb) { var count = 0; Graylog.prototype._tickDeflate = function () { - if (this._isDeflating || this.deflateQueue.isEmpty()) { + if (this._isDeflating || this._deflateQueue.isEmpty()) { return; } this._isDeflating = true; var that = this; - var msg = this.deflateQueue.getOne(); + var msg = this._deflateQueue.getOne(); function done() { that._isDeflating = false; - that.sendQueue.append(msg); + that._sendQueue.append(msg); that._tickSend(); that._tickDeflate(); } - if (!this.alwaysDeflate && msg.buff.length <= this._bufferSize) { + if (!this._alwaysDeflate && msg.buff.length <= this._bufferSize) { process.nextTick(done); return; } @@ -289,7 +318,7 @@ Graylog.prototype._tickDeflate = function () { } else { that.compressed += 1; - if (that.alwaysDeflate || compressed.length < msg.buff.length) { + if (that._alwaysDeflate || compressed.length < msg.buff.length) { msg.buff = compressed; } } @@ -304,7 +333,7 @@ Graylog.prototype._tickSend = function () { return; } - if (this.sendQueue.isEmpty()) { + if (this._sendQueue.isEmpty()) { if (!this._isDeflating) { this.emit('drain'); } @@ -314,11 +343,16 @@ Graylog.prototype._tickSend = function () { this._isSending = true; var that = this; - var msg = this.sendQueue.getOne(); + var msg = this._sendQueue.getOne(); - function done() { - that.sent += 1; + function done(error) { that._isSending = false; + + if (error) { + that.emit('error', error); + } + + that.sent += 1; that._tickSend(); } @@ -328,19 +362,19 @@ Graylog.prototype._tickSend = function () { if (buff.length <= this._bufferSize) { // No need to chunk this message - var client = this.getClient(); - var server = this.getServer(); + var client = this._getClient(); + var server = this._getServer(); + + this.emit('message', buff, server); client.send(buff, 0, buff.length, server.port, server.host, done); return; } - var that = this; - // Generate a random ID (buffer) crypto.randomBytes(8, function (error, id) { if (error) { - return cb(error); + return done(error); } that._sendChunked(id, buff, done); @@ -348,21 +382,23 @@ Graylog.prototype._tickSend = function () { }; -Graylog.prototype._log = function log(short, full, fields, timestamp, level) { - if (!this.sendQueue) { +Graylog.prototype._log = function log(level, short, full, fields, timestamp) { + if (!this._sendQueue) { + // destroyed + this.emit('warning', new Error('Trying to send on a closed client')); return; } var message = { - buff: serialize(this.hostname, this.facility, short, full, fields, timestamp, level), + buff: this._serialize(level, short, full, fields, timestamp), next: null }; - if (this.deflateQueue) { - this.deflateQueue.append(message); + if (!this._neverDeflate) { + this._deflateQueue.append(message); this._tickDeflate(); } else { - this.sendQueue.append(message); + this._sendQueue.append(message); this._tickSend(); } }; @@ -386,5 +422,6 @@ Graylog.prototype.close = function (cb) { }); }; -exports.graylog = Graylog; // deprecated -exports.Graylog = Graylog; + +module.exports = Graylog; +module.exports.graylog = Graylog; // deprecated diff --git a/lib/Queue.js b/lib/Queue.js new file mode 100644 index 0000000..642d437 --- /dev/null +++ b/lib/Queue.js @@ -0,0 +1,37 @@ +function Queue() { + this.first = null; + this.last = null; +} + +module.exports = Queue; + + +Queue.prototype.append = function (obj) { + if (this.last) { + this.last.next = obj; + this.last = obj; + } else { + this.first = this.last = obj; + } +}; + + +Queue.prototype.getOne = function () { + var result = this.first; + + if (result) { + this.first = result.next; + result.next = null; + + if (result === this.last) { + this.last = null; + } + } + + return result; +}; + + +Queue.prototype.isEmpty = function () { + return this.last === null; +}; diff --git a/package.json b/package.json index 6ba0a62..06d49a8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,10 @@ "url": "http://github.com/Wizcorp/node-graylog2/issues" }, "contributors": [ + { + "name": "Ron Korving", + "email": "rkorving@wizcorp.jp" + }, { "name": "Egor Egorov", "email": "me@egorfine.com" @@ -17,19 +21,25 @@ } ], "devDependencies": { - "jshint": "2.9.2" + "jshint": "^2.9.4", + "nyc": "^10.0.0", + "tap-spec": "^4.1.1", + "tape": "^4.6.3" }, "engines": { - "node": ">=0.6.11" + "node": ">=5.7.0" }, "homepage": "http://github.com/Wizcorp/node-graylog2", "license": "MIT", - "main": "./graylog.js", + "main": "./index.js", "repository": { "type": "git", "url": "http://github.com/Wizcorp/node-graylog2.git" }, "scripts": { - "test": "node ./test" + "test": "tape ./test/*.js | tap-spec", + "cover": "nyc tape ./test/*.js", + "lint": "jshint ./lib", + "bench": "node ./bench.js" } } diff --git a/test.js b/test.js deleted file mode 100644 index 4511065..0000000 --- a/test.js +++ /dev/null @@ -1,85 +0,0 @@ -var graylog = require('./graylog'), - fs = require('fs'), - assert = require('assert'), - file, - data, - servers = [ - { 'host': '127.0.0.1', 'port': 12201 } - ]; - - -var client = new graylog.graylog({ - servers: servers, - facility: 'Test logger / Node.JS Test Script' - }); - -console.log('---------------------------------------------'); -console.log('Sending three test as info, warning and error'); -console.log('---------------------------------------------'); -client.log('test1', 'i get this1', {cool: 'beans'}); -client.warn('test2', 'i get this2', {cool: 'beans'}); -client.error('test3', 'i get this3', {cool: 'beans'}); -client.error('customTime', 'i get this3', {cool: 'beans'}, new Date('2012-10-10 13:20:31.619Z')); -console.log(''); - -console.log('---------------------------------------------'); -console.log('Sending Sean Connery\' picture (as critical)'); -console.log('---------------------------------------------'); -file = './data/sean.jpg'; -data = fs.readFileSync(file); -client.critical('My Nice Sean Connery Picture', data.toString(), {name: 'James Bond'}); -console.log(''); - -console.log('---------------------------------------------'); -console.log('Sending data of different sizes (as critical)'); -console.log('---------------------------------------------'); -for (var i = 4; i <= 128; i *= 2) { - file = './data/' + i + '.dat'; - data = fs.readFileSync(file); - console.log('sending', file); - client.critical('Test 4 ' + file, data.toString(), {datafile: i + '.dat'}); -} -console.log(''); - -console.log('---------------------------------------------'); -console.log('Sending different parameters'); -console.log('---------------------------------------------'); -client.log('ParametersTest - Only short message'); -client.log('ParametersTest - Short message and json', {cool: 'beans'}); -client.log('ParametersTest - Short message and full message', 'Full message'); -client.log('ParametersTest - Short Message with full message and json', 'Full message', {cool: 'beans'}); -console.log(''); - -console.log('---------------------------------------------'); -console.log('Sending without deflate'); -console.log('---------------------------------------------'); -client.deflate = 'never'; -for (var i = 4; i <= 64; i *= 2) { - file = './data/' + i + '.dat'; - data = fs.readFileSync(file); - console.log('sending', file); - client.critical('Test 4 ' + file, data.toString(), {datafile: i + '.dat'}); -} -client.deflate = 'optimal'; -console.log(''); - -client.close(function () { - console.log('Insertion complete. Please check', 'http://' + servers[0].host + ':3000', 'and verify that insertion was successfull'); - console.log(''); -}); - -console.log('---------------------------------------------'); -console.log('Checking deflate assertion'); -console.log('---------------------------------------------'); -try { - new graylog.graylog({ - servers: servers, - facility: 'Test logger / Node.JS Test Script', - deflate: 'not an option' - }); - throw new Error('should not get here'); -} catch (err) { - assert( - err.message === 'deflate must be one of "optimal", "always", or "never". was "not an option"', - 'assertion msg was wrong: ' + err.message); -} diff --git a/test/chunked.js b/test/chunked.js new file mode 100644 index 0000000..a86969c --- /dev/null +++ b/test/chunked.js @@ -0,0 +1,79 @@ +var test = require('tape'); +var zlib = require('zlib'); +var Graylog = require('..'); + +test('chunked', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'never' }); + var buffers = []; + + client.on('chunk', function (header, data, i, count) { + buffers.push(data); + + t.equal(header[10], i); + t.equal(header[11], count); + + if (i === count - 1) { + var message = JSON.parse(Buffer.concat(buffers)); + + t.equal(message.short_message, 'short message'); + t.equal(message.full_message, 'full message'.repeat(1000)); + t.end(); + } + }); + + client.info('short message', 'full message'.repeat(1000)); +}); + + +test('compressed chunked', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'optimal', bufferSize: 100 }); + var buffers = []; + + client.on('chunk', function (header, data, i, count) { + buffers.push(data); + + t.equal(header[10], i); + t.equal(header[11], count); + + if (i === count - 1) { + var message = JSON.parse(zlib.inflateSync(Buffer.concat(buffers))); + + t.equal(message.short_message, 'short message'); + t.equal(message.full_message, 'full message'.repeat(1000)); + t.end(); + } + + }); + + client.info('short message', 'full message'.repeat(1000)); +}); + + +test('too big', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'never', bufferSize: 20 }); + + client.on('error', function () { + t.end(); + }); + + client.info('short message', 'full message'.repeat(1000)); +}); + + +test('crypto error', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'never' }); + var crypto = require('crypto'); + + var oldRandomBytes = crypto.randomBytes; + + crypto.randomBytes = function (n, cb) { + cb(new Error('Oops')); + }; + + client.on('error', function () { + crypto.randomBytes = oldRandomBytes; + t.end(); + }); + + client.info('short message', 'full message'.repeat(1000)); +}); diff --git a/test/close.js b/test/close.js new file mode 100644 index 0000000..99eada6 --- /dev/null +++ b/test/close.js @@ -0,0 +1,73 @@ +var test = require('tape'); +var Graylog = require('..'); + +test('drain', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'never' }); + var total = 100; + var sent = 0; + var received = 0; + + client.on('chunk', function (header, data, i, count) { + if (i === count - 1) { + received += 1; + } + }); + + client.on('drain', function () { + t.equal(sent, total); + t.equal(received, total); + t.end(); + }); + + for (var i = 0; i < total; i++) { + client.info('short message', 'full message'.repeat(1000)); + sent += 1; + } +}); + + +test('graceful close', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'never' }); + var total = 100; + var sent = 0; + var received = 0; + + client.on('chunk', function (header, data, i, count) { + if (i === count - 1) { + received += 1; + } + }); + + for (var i = 0; i < total; i++) { + client.info('short message', 'full message'.repeat(1000)); + sent += 1; + } + + client.close(function () { + t.equal(sent, total); + t.equal(received, total); + t.end(); + }); +}); + + +test('instant close', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }] }); + + client.close(function () { + t.end(); + }); +}); + + +test('send after close', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }] }); + + client.on('warning', function () { + t.end(); + }); + + client.close(function () { + client.info('short message'); + }); +}); diff --git a/test/compressed.js b/test/compressed.js new file mode 100644 index 0000000..732b2a7 --- /dev/null +++ b/test/compressed.js @@ -0,0 +1,49 @@ +var test = require('tape'); +var zlib = require('zlib'); +var Graylog = require('..'); + +test('compressed', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'optimal' }); + + client.on('message', function (message) { + message = JSON.parse(zlib.inflateSync(message)); + + t.equal(message.short_message, 'short message'); + t.equal(message.full_message, 'full message'.repeat(1000)); + t.end(); + }); + + client.info('short message', 'full message'.repeat(1000)); +}); + + +test('forced compressed', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'always' }); + + client.on('message', function (message) { + message = JSON.parse(zlib.inflateSync(message)); + + t.equal(message.short_message, 'short message'); + t.equal(message.full_message, 'full message'); + t.end(); + }); + + client.info('short message', 'full message'); +}); + + +test('compression error handling', function (t) { + var oldDeflate = zlib.deflate; + zlib.deflate = function (buff, cb) { + cb(new Error('Oops')); + }; + + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], deflate: 'always' }); + + client.on('warning', function (error) { + zlib.deflate = oldDeflate; + t.end(); + }); + + client.info('short message', 'full message'); +}); diff --git a/test/levels.js b/test/levels.js new file mode 100644 index 0000000..a16207c --- /dev/null +++ b/test/levels.js @@ -0,0 +1,41 @@ +var test = require('tape'); +var Graylog = require('..'); + +test('levels', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }] }); + + var levels = { + debug: 7, + info: 6, + notice: 5, + warning: 4, + error: 3, + critical: 2, + alert: 1, + emergency: 0 + }; + + var levelNames = Object.keys(levels); + var cursor = 0; // traverses over levelNames + + client.on('message', function (message) { + message = JSON.parse(message); + + var expectedLevelName = levelNames[cursor]; + var expectedLevel = levels[expectedLevelName]; + + t.equal(message.short_message, 'short message for level ' + expectedLevelName); + t.equal(message.level, expectedLevel); + + cursor += 1; + + if (cursor === levelNames.length) { + t.end(); + } + }); + + for (var i = 0; i < levelNames.length; i += 1) { + var level = levelNames[i]; + client[level]('short message for level ' + level); + } +}); diff --git a/test/options.js b/test/options.js new file mode 100644 index 0000000..d35b8dd --- /dev/null +++ b/test/options.js @@ -0,0 +1,86 @@ +var test = require('tape'); +var Graylog = require('..'); + +test('options', function (t) { + t.throws(function () { + new Graylog(); + }); + + t.throws(function () { + new Graylog({ servers: 'foo' }); + }); + + t.throws(function () { + new Graylog({ servers: [] }); + }); + + t.throws(function () { + new Graylog({ servers: ['foo'] }); + }); + + t.throws(function () { + new Graylog({ servers: [{}] }); + }); + + t.throws(function () { + new Graylog({ servers: [{ host: 1, port: 1 }] }); + }); + + t.throws(function () { + new Graylog({ servers: [{ host: 0, port: 'bar' }] }); + }); + + t.throws(function () { + new Graylog({ servers: [{ host: 'foo', port: 'foo' }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.throws(function () { + new Graylog({ hostname: 1, servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ hostname: 'bar', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.throws(function () { + new Graylog({ facility: 1, servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ facility: 'bar', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.throws(function () { + new Graylog({ bufferSize: 'foo', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ bufferSize: 100, servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.throws(function () { + new Graylog({ deflate: 1, servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.throws(function () { + new Graylog({ deflate: 'foo', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ deflate: 'optimal', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ deflate: 'always', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.doesNotThrow(function () { + new Graylog({ deflate: 'never', servers: [{ host: 'foo', port: 12345 }] }); + }); + + t.end(); +}); diff --git a/test/small.js b/test/small.js new file mode 100644 index 0000000..f7734c6 --- /dev/null +++ b/test/small.js @@ -0,0 +1,67 @@ +var test = require('tape'); +var Graylog = require('..'); + +test('small', function (t) { + var client = new Graylog({ servers: [{ host: '127.0.0.1', port: 12345 }], hostname: 'foo', facility: 'bar' }); + + client.on('message', function (message) { + message = JSON.parse(message); + + t.equal(message.version, '1.0'); + t.equal(typeof message.timestamp, 'number'); + t.equal(message.host, 'foo'); + t.equal(message.facility, 'bar'); + t.equal(message.level, client.level.INFO) + t.equal(message.short_message, 'short message'); + t.equal(message.full_message, 'full message'); + t.equal(message.__id, 1); + t.equal(message._foo, 'bar'); + t.end(); + }); + + client.info('short message', 'full message', { id: 1, foo: 'bar' }); +}); + + +test('multiserver', function (t) { + var servers = [ + { host: '127.0.0.1', port: 12345 }, + { host: '127.0.0.1', port: 12345 } + ]; + + var client = new Graylog({ servers: servers }); + var cursor = 0; + var count = 0; + var total = 5; + + client.on('message', function (message, server) { + t.equal(server, servers[cursor]); + + cursor += 1; + count += 1; + + if (cursor >= servers.length) { + cursor = 0; + } + + if (count === total) { + t.end(); + } + }); + + for (var i = 0; i < total; i += 1) { + client.info('short message'); + } +}); + + +test('bad host', function (t) { + var client = new Graylog({ servers: [{ host: 'foobar', port: 12345 }] }); + + client.on('error', function (error) { + t.equal(error.code, 'ENOTFOUND'); + t.end(); + }); + + client.info('short message'); +});