UNPKG

bunyan-logstash-tcp

Version:
609 lines (495 loc) 21.6 kB
'use strict'; 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' }); }); }); }); });