bunyan-logstash-tcp
Version:
Logstash TCP plugin for Bunyan
609 lines (495 loc) • 21.6 kB
JavaScript
;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var os = require('os');
var fs = require('fs');
var net = require('net');
var sinon = require('sinon');
var tls = require('tls');
var CBuffer = require('CBuffer');
var _require = require('chai'),
expect = _require.expect;
var _require2 = require('../lib/logstash'),
createStream = _require2.createStream;
var EventEmitter = require('events').EventEmitter;
var MockSocket = function () {
function MockSocket() {
_classCallCheck(this, MockSocket);
this.listeners = {};
this.unrefCalled = false;
this.destroyCalled = false;
this.encoding = null;
this.host = null;
this.port = null;
this.content = '';
}
_createClass(MockSocket, [{
key: 'connect',
value: function connect(host, port, callback) {
this.host = host;
this.port = port;
setTimeout(callback);
}
}, {
key: 'on',
value: function on(event, callback) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push(callback);
}
}, {
key: 'unref',
value: function unref() {
this.unrefCalled = true;
}
}, {
key: 'destroy',
value: function destroy() {
this.destroyCalled = true;
}
}, {
key: 'setEncoding',
value: function setEncoding(encoding) {
this.encoding = encoding;
}
}, {
key: 'write',
value: function write(text) {
this.content += text;
}
}, {
key: 'dispatchEvent',
value: function dispatchEvent(event) {
for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
params[_key - 1] = arguments[_key];
}
(this.listeners[event] || []).forEach(function (callback) {
return callback.apply(undefined, params);
});
}
}]);
return MockSocket;
}();
describe('logstash', function () {
var sandbox = sinon.sandbox.create();
beforeEach(function () {
sandbox.stub(net, 'Socket').callsFake(function () {
return new MockSocket();
});
});
afterEach(function () {
sandbox.restore();
});
describe('createStream', function () {
it('Should call EventEmitter.call()', function () {
var callSpy = sandbox.spy(EventEmitter, 'call');
var stream = createStream();
expect(callSpy.calledWith(stream));
});
it('Should call apply default options when no options are provided', function () {
var stream = createStream();
expect(stream).to.have.property('name', 'bunyan');
expect(stream).to.have.property('level', 'info');
expect(stream).to.have.property('server', os.hostname());
expect(stream).to.have.property('host', '127.0.0.1');
expect(stream).to.have.property('port', 9999);
expect(stream).to.have.property('application', process.title);
expect(stream).to.have.property('pid', process.pid);
expect(stream).to.have.property('tags').eql(['bunyan']);
expect(stream).to.have.property('type', undefined);
expect(stream).to.have.property('client', null);
expect(stream).to.have.property('ssl_enable', false);
expect(stream).to.have.property('ssl_key', '');
expect(stream).to.have.property('ssl_cert', '');
expect(stream).to.have.property('ca', '');
expect(stream).to.have.property('ssl_passphrase', '');
expect(stream).to.have.property('cbuffer_size', 10);
expect(stream).to.have.property('log_queue').instanceOf(CBuffer).deep.property('data.length', 10);
expect(stream).to.have.property('connected', false);
expect(stream).to.have.property('socket').instanceOf(MockSocket);
expect(stream).to.have.property('retries', 0);
expect(stream).to.have.property('max_connect_retries', 4);
expect(stream).to.have.property('retry_interval', 100);
});
it('Should use the level option value', function () {
var stream = createStream({ level: 'debug' });
expect(stream).to.have.property('level', 'debug');
});
it('Should use the server option value', function () {
var stream = createStream({ server: 'myServer.com' });
expect(stream).to.have.property('server', 'myServer.com');
});
it('Should use the host option value', function () {
var stream = createStream({ host: 'host1' });
expect(stream).to.have.property('host', 'host1');
});
it('Should use the port option value', function () {
var stream = createStream({ port: 12345 });
expect(stream).to.have.property('port', 12345);
});
it('Should use the appName option value', function () {
var stream = createStream({ appName: 'my app' });
expect(stream).to.have.property('application', 'my app');
});
it('Should use the pid option value', function () {
var stream = createStream({ pid: 123456 });
expect(stream).to.have.property('pid', 123456);
});
it('Should use the tags option value', function () {
var stream = createStream({ tags: ['tag1', 'tag2'] });
expect(stream).to.have.property('tags').eql(['tag1', 'tag2']);
});
it('Should use the type option value', function () {
var stream = createStream({ type: 'alpha' });
expect(stream).to.have.property('type', 'alpha');
});
it('Should use the ssl_enable option value', function () {
sandbox.stub(tls, 'connect').callsFake(function () {
return new MockSocket();
});
var stream = createStream({ ssl_enable: true });
expect(stream).to.have.property('ssl_enable', true);
});
it('Should use the ssl_key option value', function () {
var stream = createStream({ ssl_key: 'myKey' });
expect(stream).to.have.property('ssl_key', 'myKey');
});
it('Should use the ssl_cert option value', function () {
var stream = createStream({ ssl_cert: 'myCert' });
expect(stream).to.have.property('ssl_cert', 'myCert');
});
it('Should use the ca option value', function () {
var stream = createStream({ ca: 'caaaa' });
expect(stream).to.have.property('ca', 'caaaa');
});
it('Should use the ssl_passphrase option value', function () {
var stream = createStream({ ssl_passphrase: 'pass' });
expect(stream).to.have.property('ssl_passphrase', 'pass');
});
it('Should use the cbuffer_size option value', function () {
var stream = createStream({ cbuffer_size: 14 });
expect(stream).to.have.property('cbuffer_size', 14);
expect(stream).to.have.property('log_queue').instanceOf(CBuffer).deep.property('data.length', 14);
});
it('Should use the numeric max_connect_retries option value', function () {
var stream = createStream({ max_connect_retries: 12 });
expect(stream).to.have.property('max_connect_retries', 12);
});
it('Should ignore the non numeric max_connect_retries option value', function () {
var stream = createStream({ max_connect_retries: '12' });
expect(stream).to.have.property('max_connect_retries', 4);
});
it('Should use the retry_interval option value', function () {
var stream = createStream({ retry_interval: 200 });
expect(stream).to.have.property('retry_interval', 200);
});
it('Should inherit EventEmitter', function () {
var stream = createStream();
expect(stream).to.be.instanceOf(EventEmitter);
});
});
describe('LogstashStream', function () {
describe('write', function () {
it('Should call the send method', function () {
var stream = createStream({
tags: ['tag1', 'tag2'],
server: 'myServer.com',
appName: 'myApp',
pid: 12345
});
var sendStub = sandbox.stub(stream, 'send');
stream.write({
time: '2017-05-06T16:48:19.763Z'
});
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('@timestamp', '2017-05-06T16:48:19.763Z');
expect(payload).to.have.property('tags').eql(['tag1', 'tag2']);
expect(payload).to.have.property('source', 'myServer.com/myApp');
expect(payload).to.have.property('pid', 12345);
});
it('Should override the default message fields with the entry fields', function () {
var stream = createStream({
tags: ['tag1', 'tag2'],
server: 'myServer.com',
appName: 'myApp',
pid: 12345
});
var sendStub = sandbox.stub(stream, 'send');
stream.write({
time: '2017-05-06T16:48:19.763Z',
'@timestamp': 'overriden timestamp',
msg: 'my message',
tags: ['overriden tag'],
source: 'overriden source',
level: 'debug',
myKey: 'myValue'
});
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('@timestamp', 'overriden timestamp');
expect(payload).to.have.property('tags').eql(['overriden tag']);
expect(payload).to.have.property('source', 'overriden source');
expect(payload).to.have.property('myKey', 'myValue');
expect(payload).to.have.property('pid', 12345);
expect(payload).to.have.property('level', 'debug');
expect(payload).to.have.property('message', 'my message');
});
it('Should accept a string containing valid JSON', function () {
var stream = createStream({});
var sendStub = sandbox.stub(stream, 'send');
stream.write('{"time":"2017-05-06T16:48:19.763Z"}');
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('@timestamp', '2017-05-06T16:48:19.763Z');
});
it('Should transform valid numeric levels into their string version', function () {
var stream = createStream({});
var sendStub = sandbox.stub(stream, 'send');
stream.write({ time: '2017-05-06T16:48:19.763Z', level: 50 });
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('level', 'error');
});
it('Should keep unknown numeric levels as is', function () {
var stream = createStream({});
var sendStub = sandbox.stub(stream, 'send');
stream.write({ time: '2017-05-06T16:48:19.763Z', level: 51 });
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('level', 51);
});
it('Should include string stream type in the message', function () {
var stream = createStream({ type: 'alpha' });
var sendStub = sandbox.stub(stream, 'send');
stream.write({ time: '2017-05-06T16:48:19.763Z', level: 51 });
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('type', 'alpha');
});
it('Should not include non string stream type in the message', function () {
var stream = createStream({ type: 42 });
var sendStub = sandbox.stub(stream, 'send');
stream.write({ time: '2017-05-06T16:48:19.763Z', level: 51 });
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.not.have.property('type');
});
it('Should not override stream pid with entry pid if any', function () {
var stream = createStream({ pid: 42 });
var sendStub = sandbox.stub(stream, 'send');
stream.write({ time: '2017-05-06T16:48:19.763Z', pid: 43 });
expect(sendStub.callCount).to.eql(1);
var args = sendStub.getCall(0).args;
expect(args).to.have.length(1);
var payload = JSON.parse(args[0]);
expect(payload).to.have.property('pid', 42);
});
});
describe('connect', function () {
it('Should create non tls socket when ssl_enable is false', function () {
var tlsConnectSpy = sandbox.spy(tls, 'connect');
createStream();
expect(tlsConnectSpy.callCount).to.eql(0);
});
it('Should create tls socket when ssl_enable is true', function () {
var tlsConnectStub = sandbox.stub(tls, 'connect').callsFake(function () {
return new MockSocket();
});
sandbox.stub(fs, 'readFileSync').callsFake(function (path) {
return 'content of ' + path;
});
var stream = createStream({
ssl_enable: true,
ssl_key: 'path/to/key',
ssl_cert: 'path/to/cert',
ssl_passphrase: 'passphrase',
ca: ['path/to/ca1', 'path/to/ca2'],
port: 12345,
host: 'sslhost'
});
expect(tlsConnectStub.callCount).to.eql(1);
var args = tlsConnectStub.getCall(0).args;
expect(args).to.have.length(4);
expect(args[0]).to.eql(12345);
expect(args[1]).to.eql('sslhost');
expect(args[2]).to.eql({
key: 'content of path/to/key',
cert: 'content of path/to/cert',
passphrase: 'passphrase',
ca: ['content of path/to/ca1', 'content of path/to/ca2']
});
var connectedCallback = args[3];
expect(stream).to.have.property('connecting', true);
expect(stream).to.have.property('connected', false);
connectedCallback();
expect(stream).to.have.property('connecting', false);
expect(stream).to.have.deep.property('socket.encoding', 'UTF-8');
expect(stream).to.have.property('connected', true);
});
it('Should hanlde socket errors', function () {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({ ssl_enable: true });
var streamEmitStub = sandbox.stub(stream, 'emit');
var socket = stream.socket;
stream.socket.dispatchEvent('error', 'test error');
expect(stream).to.have.property('connecting', false);
expect(stream).to.have.property('connected', false);
expect(socket).to.have.property('destroyCalled', true);
expect(stream).to.have.property('socket', null);
expect(streamEmitStub.withArgs('error', 'test error').callCount).to.equal(1);
});
it('Should hanlde socket timeouts', function () {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({ ssl_enable: true });
var streamEmitStub = sandbox.stub(stream, 'emit');
var socket = stream.socket;
stream.socket.dispatchEvent('timeout');
expect(socket).to.have.property('destroyCalled', true);
expect(streamEmitStub.withArgs('timeout').callCount).to.equal(1);
});
it('Should hanlde socket timeouts with readyState still "open"', function () {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({ ssl_enable: true });
stream.socket.readyState = 'open';
var streamEmitStub = sandbox.stub(stream, 'emit');
var socket = stream.socket;
stream.socket.dispatchEvent('timeout');
expect(socket).to.have.property('destroyCalled', false);
expect(streamEmitStub.withArgs('timeout').callCount).to.equal(1);
});
it('Should hanlde connect event', function () {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({ ssl_enable: true });
var streamEmitStub = sandbox.stub(stream, 'emit');
stream.socket.dispatchEvent('connect');
expect(stream).to.have.property('retries', 0);
expect(streamEmitStub.withArgs('connect').callCount).to.equal(1);
});
it('Should hanlde close event when retries are still possible', function (done) {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({
ssl_enable: true,
max_connect_retries: 1,
retry_interval: 1
});
var streamEmitStub = sandbox.stub(stream, 'emit');
stream.connecting = false;
stream.retries = 0;
stream.socket.dispatchEvent('close');
expect(streamEmitStub.withArgs('close').callCount).to.equal(1);
expect(stream).to.not.have.property('silent');
sandbox.stub(stream, 'connect').callsFake(done);
});
it('Should hanlde close event when retries are still possible but it\'s already connecting', function (done) {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({
ssl_enable: true,
max_connect_retries: 1,
retry_interval: 1
});
var streamEmitStub = sandbox.stub(stream, 'emit');
stream.connecting = true;
stream.retries = 0;
stream.socket.dispatchEvent('close');
expect(streamEmitStub.withArgs('close').callCount).to.equal(1);
expect(stream).to.not.have.property('silent');
var connectStub = sandbox.stub(stream, 'connect');
setTimeout(function () {
expect(connectStub.callCount).to.equal(0);
done();
}, 10);
});
it('Should hanlde close event when no more retries are available', function () {
sandbox.stub(tls, 'connect').callsFake(function (port, host, options, callback) {
setTimeout(callback);
return new MockSocket();
});
var stream = createStream({
ssl_enable: true,
max_connect_retries: 1,
retry_interval: 0
});
var streamEmitStub = sandbox.stub(stream, 'emit');
stream.connecting = false;
stream.retries = 1;
var oldLogQueue = stream.log_queue;
stream.socket.dispatchEvent('close');
expect(streamEmitStub.withArgs('close').callCount).to.equal(1);
expect(stream.log_queue !== oldLogQueue);
expect(stream).to.have.property('silent', true);
});
});
describe('flush', function () {
it('Should send all messages and leave queue empty', function () {
var stream = createStream();
stream.log_queue.push({ message: 'a' });
stream.log_queue.push({ message: 'b' });
var sendStub = sandbox.stub(stream, 'sendLog');
stream.flush();
expect(sendStub.callCount).to.equal(2);
expect(sendStub.withArgs('a').callCount).to.equal(1);
expect(sendStub.withArgs('b').callCount).to.equal(1);
expect(stream.log_queue.toArray()).to.have.length(0);
});
});
describe('sendLog', function () {
it('Should write log to the socket', function () {
var stream = createStream();
stream.sendLog('hello');
expect(stream.socket.content).to.equal('hello\n');
});
});
describe('send', function () {
it('Should write log to the socket when connected', function () {
var stream = createStream();
stream.connected = true;
var sendLogStub = sandbox.stub(stream, 'sendLog');
stream.send('hello');
expect(sendLogStub.callCount).to.equal(1);
expect(sendLogStub.withArgs('hello').callCount).to.equal(1);
});
});
describe('send', function () {
it('Should store log in the queue when not connected', function () {
var stream = createStream();
stream.connected = false;
var sendLogStub = sandbox.stub(stream, 'sendLog');
stream.send('hello');
expect(sendLogStub.callCount).to.equal(0);
expect(stream.log_queue.pop()).to.eql({ message: 'hello' });
});
});
});
});