log4js-node/test/tap/multiprocess-test.js
2017-11-14 08:45:22 +11:00

363 lines
10 KiB
JavaScript

'use strict';
const test = require('tap').test;
const sandbox = require('sandboxed-module');
const recording = require('../../lib/appenders/recording');
function makeFakeNet() {
return {
data: [],
cbs: {},
createConnectionCalled: 0,
createConnection: function (port, host) {
const fakeNet = this;
this.port = port;
this.host = host;
this.createConnectionCalled += 1;
return {
on: function (evt, cb) {
fakeNet.cbs[evt] = cb;
},
write: function (data, encoding) {
fakeNet.data.push(data);
fakeNet.encoding = encoding;
},
end: function () {
fakeNet.closeCalled = true;
}
};
},
createServer: function (cb) {
const fakeNet = this;
cb({
remoteAddress: '1.2.3.4',
remotePort: '1234',
setEncoding: function (encoding) {
fakeNet.encoding = encoding;
},
on: function (event, cb2) {
fakeNet.cbs[event] = cb2;
}
});
return {
listen: function (port, host) {
fakeNet.port = port;
fakeNet.host = host;
}
};
}
};
}
test('Multiprocess Appender', (batch) => {
batch.beforeEach((done) => {
recording.erase();
done();
});
batch.test('worker', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
log4js.configure({
appenders: {
worker: {
type: 'multiprocess', mode: 'worker', loggerPort: 1234, loggerHost: 'pants'
}
},
categories: { default: { appenders: ['worker'], level: 'trace' } }
});
const logger = log4js.getLogger();
logger.info('before connect');
fakeNet.cbs.connect();
logger.info('after connect');
fakeNet.cbs.close(true);
logger.info('after error, before connect');
fakeNet.cbs.connect();
logger.info('after error, after connect');
logger.error(new Error('Error test'));
const net = fakeNet;
t.test('should open a socket to the loggerPort and loggerHost', (assert) => {
assert.equal(net.port, 1234);
assert.equal(net.host, 'pants');
assert.end();
});
t.test('should buffer messages written before socket is connected', (assert) => {
assert.include(net.data[0], JSON.stringify('before connect'));
assert.end();
});
t.test('should write log messages to socket as json strings with a terminator string', (assert) => {
assert.include(net.data[0], JSON.stringify('before connect'));
assert.equal(net.data[1], '__LOG4JS__');
assert.include(net.data[2], JSON.stringify('after connect'));
assert.equal(net.data[3], '__LOG4JS__');
assert.equal(net.encoding, 'utf8');
assert.end();
});
t.test('should attempt to re-open the socket on error', (assert) => {
assert.include(net.data[4], JSON.stringify('after error, before connect'));
assert.equal(net.data[5], '__LOG4JS__');
assert.include(net.data[6], JSON.stringify('after error, after connect'));
assert.equal(net.data[7], '__LOG4JS__');
assert.equal(net.createConnectionCalled, 2);
assert.end();
});
t.test('should serialize an Error correctly', (assert) => {
assert.ok(
JSON.parse(net.data[8]).data[0].stack,
`Expected:\n\n${net.data[8]}\n\n to have a 'data[0].stack' property`
);
const actual = JSON.parse(net.data[8]).data[0].stack;
assert.match(actual, /^Error: Error test/);
assert.end();
});
t.end();
});
batch.test('worker with timeout', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
log4js.configure({
appenders: { worker: { type: 'multiprocess', mode: 'worker' } },
categories: { default: { appenders: ['worker'], level: 'trace' } }
});
const logger = log4js.getLogger();
logger.info('before connect');
fakeNet.cbs.connect();
logger.info('after connect');
fakeNet.cbs.timeout();
logger.info('after timeout, before close');
fakeNet.cbs.close();
logger.info('after close, before connect');
fakeNet.cbs.connect();
logger.info('after close, after connect');
const net = fakeNet;
t.test('should attempt to re-open the socket', (assert) => {
// skipping the __LOG4JS__ separators
assert.include(net.data[0], JSON.stringify('before connect'));
assert.include(net.data[2], JSON.stringify('after connect'));
assert.include(net.data[4], JSON.stringify('after timeout, before close'));
assert.include(net.data[6], JSON.stringify('after close, before connect'));
assert.include(net.data[8], JSON.stringify('after close, after connect'));
assert.equal(net.createConnectionCalled, 2);
assert.end();
});
t.end();
});
batch.test('worker defaults', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
log4js.configure({
appenders: { worker: { type: 'multiprocess', mode: 'worker' } },
categories: { default: { appenders: ['worker'], level: 'trace' } }
});
t.test('should open a socket to localhost:5000', (assert) => {
assert.equal(fakeNet.port, 5000);
assert.equal(fakeNet.host, 'localhost');
assert.end();
});
t.end();
});
batch.test('master', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet,
'./appenders/recording': recording
}
}
);
log4js.configure({
appenders: {
recorder: { type: 'recording' },
master: {
type: 'multiprocess',
mode: 'master',
loggerPort: 1234,
loggerHost: 'server',
appender: 'recorder'
}
},
categories: { default: { appenders: ['master'], level: 'trace' } }
});
const net = fakeNet;
t.test('should listen for log messages on loggerPort and loggerHost', (assert) => {
assert.equal(net.port, 1234);
assert.equal(net.host, 'server');
assert.end();
});
t.test('should return the underlying appender', (assert) => {
log4js.getLogger().info('this should be sent to the actual appender directly');
assert.equal(recording.replay()[0].data[0], 'this should be sent to the actual appender directly');
assert.end();
});
t.test('when a client connects', (assert) => {
const logString = `${JSON.stringify({
level: { level: 10000, levelStr: 'DEBUG' },
data: ['some debug']
})}__LOG4JS__`;
net.cbs.data(`${JSON.stringify({
level: { level: 40000, levelStr: 'ERROR' },
data: ['an error message']
})}__LOG4JS__`);
net.cbs.data(logString.substring(0, 10));
net.cbs.data(logString.substring(10));
net.cbs.data(logString + logString + logString);
net.cbs.end(`${JSON.stringify({
level: { level: 50000, levelStr: 'FATAL' },
data: ["that's all folks"]
})}__LOG4JS__`);
net.cbs.data('bad message__LOG4JS__');
const logEvents = recording.replay();
// should parse log messages into log events and send to appender
assert.equal(logEvents[0].level.toString(), 'ERROR');
assert.equal(logEvents[0].data[0], 'an error message');
assert.equal(logEvents[0].remoteAddress, '1.2.3.4');
assert.equal(logEvents[0].remotePort, '1234');
// should parse log messages split into multiple chunks'
assert.equal(logEvents[1].level.toString(), 'DEBUG');
assert.equal(logEvents[1].data[0], 'some debug');
assert.equal(logEvents[1].remoteAddress, '1.2.3.4');
assert.equal(logEvents[1].remotePort, '1234');
// should parse multiple log messages in a single chunk'
assert.equal(logEvents[2].data[0], 'some debug');
assert.equal(logEvents[3].data[0], 'some debug');
assert.equal(logEvents[4].data[0], 'some debug');
// should handle log messages sent as part of end event'
assert.equal(logEvents[5].data[0], "that's all folks");
// should handle unparseable log messages
assert.equal(logEvents[6].level.toString(), 'ERROR');
assert.equal(logEvents[6].categoryName, 'log4js');
assert.equal(logEvents[6].data[0], 'Unable to parse log:');
assert.equal(logEvents[6].data[1], 'bad message');
assert.end();
});
t.end();
});
batch.test('master without actual appender throws error', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
t.throws(
() =>
log4js.configure({
appenders: { master: { type: 'multiprocess', mode: 'master' } },
categories: { default: { appenders: ['master'], level: 'trace' } }
}),
new Error('multiprocess master must have an "appender" defined')
);
t.end();
});
batch.test('master with unknown appender throws error', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
t.throws(
() =>
log4js.configure({
appenders: { master: { type: 'multiprocess', mode: 'master', appender: 'cheese' } },
categories: { default: { appenders: ['master'], level: 'trace' } }
}),
new Error('multiprocess master appender "cheese" not defined')
);
t.end();
});
batch.test('master defaults', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
log4js.configure({
appenders: {
stdout: { type: 'stdout' },
master: { type: 'multiprocess', mode: 'master', appender: 'stdout' }
},
categories: { default: { appenders: ['master'], level: 'trace' } }
});
t.test('should listen for log messages on localhost:5000', (assert) => {
assert.equal(fakeNet.port, 5000);
assert.equal(fakeNet.host, 'localhost');
assert.end();
});
t.end();
});
batch.end();
});